Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AzureMonitorExporter] Fix how arrays stored in Activity Tags are serialized. #34086

Merged
merged 2 commits into from
Feb 11, 2023
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Text;

namespace Azure.Monitor.OpenTelemetry.Exporter.Internals
{
internal static class ArrayExtensions
{
/// <summary>
/// Builds a comma delimited string of the components of an array.
/// </summary>
/// <remarks>
/// For example: new int[] { 1, 2, 3 } would be returned as "1,2,3".
/// </remarks>
/// <param name="input">An array to be evaluated.</param>
/// <returns>A comma delimited string of the components of the input array.</returns>
[return: System.Diagnostics.CodeAnalysis.NotNullIfNotNull(nameof(input))]
public static string? ToCommaDelimitedString(this Array? input)
{
if (input == null)
{
return null;
}

StringBuilder sb = new(input.Length);
foreach (var item in input)
{
// TODO: Consider changing it to JSon array.
if (item != null)
{
sb.Append(item);
sb.Append(',');
}
}

// remove trailing comma
if (sb.Length > 0)
{
sb.Length--;
}

return sb.ToString();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,23 +80,7 @@ public void ForEach(IEnumerable<KeyValuePair<string, object>> activityTags)

if (activityTag.Value is Array array)
{
StringBuilder sw = new StringBuilder();
foreach (var item in array)
{
// TODO: Consider changing it to JSon array.
if (item != null)
{
sw.Append(item);
sw.Append(',');
}
}

if (sw.Length > 0)
{
sw.Length--;
}

AzMonList.Add(ref UnMappedTags, new KeyValuePair<string, object>(activityTag.Key, sw.ToString()));
AzMonList.Add(ref UnMappedTags, new KeyValuePair<string, object>(activityTag.Key, array.ToCommaDelimitedString()));
continue;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,14 @@ private static MonitorBase GetTraceTelemetryData(ActivityEvent activityEvent)

foreach (var tag in activityEvent.Tags)
{
messageData.Properties.Add(tag.Key, tag.Value.ToString());
if (tag.Value is Array arrayValue)
{
messageData.Properties.Add(tag.Key, arrayValue.ToCommaDelimitedString());
}
else
{
messageData.Properties.Add(tag.Key, tag.Value.ToString());
}
}

return new MonitorBase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ namespace Azure.Monitor.OpenTelemetry.Exporter.Tests.CommonTestFramework
{
internal static class TelemetryItemValidationHelper
{
public static void AssertLog_As_MessageTelemetry(
public static void AssertMessageTelemetry(
TelemetryItem telemetryItem,
string expectedSeverityLevel,
string expectedMessage,
IDictionary<string, string> expectedMeessageProperties,
IDictionary<string, string> expectedMessageProperties,
string expectedSpanId,
string expectedTraceId)
{
Expand All @@ -43,10 +43,19 @@ public static void AssertLog_As_MessageTelemetry(
Assert.Contains("ai.internal.sdkVersion", telemetryItem.Tags.Keys);

var messageData = (MessageData)telemetryItem.Data.BaseData;
Assert.Equal(expectedSeverityLevel, messageData.SeverityLevel);

if (expectedSeverityLevel == null)
{
Assert.Null(messageData.SeverityLevel);
}
else
{
Assert.Equal(expectedSeverityLevel, messageData.SeverityLevel);
}

Assert.Equal(expectedMessage, messageData.Message);

foreach (var prop in expectedMeessageProperties)
foreach (var prop in expectedMessageProperties)
{
Assert.Equal(prop.Value, messageData.Properties[prop.Key]);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ public void VerifyLog(LogLevel logLevel, string expectedSeverityLevel)
this.telemetryOutput.Write(telemetryItems);
var telemetryItem = telemetryItems.Single();

TelemetryItemValidationHelper.AssertLog_As_MessageTelemetry(
TelemetryItemValidationHelper.AssertMessageTelemetry(
telemetryItem: telemetryItem,
expectedSeverityLevel: expectedSeverityLevel,
expectedMessage: "Hello {name}.",
expectedMeessageProperties: new Dictionary<string, string> { { "name", "World" }},
expectedMessageProperties: new Dictionary<string, string> { { "name", "World" }},
expectedSpanId: null,
expectedTraceId: null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,11 +256,63 @@ public void VerifyLogWithinActivity(LogLevel logLevel, string expectedSeverityLe
this.telemetryOutput.Write(logTelemetryItems);
var logTelemetryItem = logTelemetryItems.Single();

TelemetryItemValidationHelper.AssertLog_As_MessageTelemetry(
TelemetryItemValidationHelper.AssertMessageTelemetry(
telemetryItem: logTelemetryItem,
expectedSeverityLevel: expectedSeverityLevel,
expectedMessage: "Hello {name}.",
expectedMeessageProperties: new Dictionary<string, string> { { "name", "World" } },
expectedMessageProperties: new Dictionary<string, string> { { "name", "World" } },
expectedSpanId: spanId,
expectedTraceId: traceId);
}

[Fact]
public void TestActivityEvents()
{
// SETUP
var uniqueTestId = Guid.NewGuid();

var activitySourceName = $"activitySourceName{uniqueTestId}";
using var activitySource = new ActivitySource(activitySourceName);

var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddSource(activitySourceName)
.AddAzureMonitorTraceExporterForTest(out ConcurrentBag<TelemetryItem> telemetryItems)
.Build();

// ACT
string spanId = null, traceId = null;

using (var activity = activitySource.StartActivity(name: "ActivityWithException"))
{
traceId = activity.TraceId.ToHexString();
spanId = activity.SpanId.ToHexString();

var eventTags = new Dictionary<string, object>
{
{ "integer", 1 },
{ "string", "Hello, World!" },
{ "intArray", new int[] { 1, 2, 3 } }
};
activity?.AddEvent(new("Gonna try it!", DateTimeOffset.Now, new(eventTags)));
}

// CLEANUP
tracerProvider.Dispose();

// ASSERT
Assert.True(telemetryItems.Any(), "Unit test failed to collect telemetry.");
this.telemetryOutput.Write(telemetryItems);

var messageTelemetryItem = telemetryItems.First(x => x.Name == "Message");

TelemetryItemValidationHelper.AssertMessageTelemetry(
telemetryItem: messageTelemetryItem,
expectedSeverityLevel: null,
expectedMessage: "Gonna try it!",
expectedMessageProperties: new Dictionary<string, string> {
{ "integer", "1" },
{ "string", "Hello, World!" },
{ "intArray", "1,2,3" } },
expectedSpanId: spanId,
expectedTraceId: traceId);
}
Expand Down