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

[Part2] Support Activity Status and status description in Jaeger Exporter. #3073

Merged
merged 9 commits into from
Mar 24, 2022
Merged
Show file tree
Hide file tree
Changes from 4 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
6 changes: 6 additions & 0 deletions src/OpenTelemetry.Exporter.Jaeger/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## Unreleased

* Added support for Activity Status and StatusDescription which were
added to Activity from version 6.0. To maintain backward
compatibility, the exporter falls back to checking status inside
the tag "otel.status_code".
([#3073](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3073))

## 1.2.0-rc3

Released 2022-Mar-04
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,42 @@ public static JaegerSpan ToJaegerSpan(this Activity activity)

activity.EnumerateTags(ref jaegerTags);

if (activity.Status != ActivityStatusCode.Unset)
Yun-Ting marked this conversation as resolved.
Show resolved Hide resolved
{
if (activity.Status == ActivityStatusCode.Ok)
{
PooledList<JaegerTag>.Add(
ref jaegerTags.Tags,
new JaegerTag(SpanAttributeConstants.StatusCodeKey, JaegerTagType.STRING, vStr: "OK"));
}
else
{
PooledList<JaegerTag>.Add(
ref jaegerTags.Tags,
new JaegerTag(SpanAttributeConstants.StatusCodeKey, JaegerTagType.STRING, vStr: "ERROR"));

PooledList<JaegerTag>.Add(
ref jaegerTags.Tags,
new JaegerTag(JaegerErrorFlagTagName, JaegerTagType.BOOL, vBool: true));
}
}
else if (jaegerTags.StatusCode.HasValue && jaegerTags.StatusCode != StatusCode.Unset)
{
PooledList<JaegerTag>.Add(
ref jaegerTags.Tags,
new JaegerTag(
SpanAttributeConstants.StatusCodeKey,
JaegerTagType.STRING,
vStr: StatusHelper.GetTagValueForStatusCode(jaegerTags.StatusCode.Value)));

if (jaegerTags.StatusCode == StatusCode.Error)
{
PooledList<JaegerTag>.Add(
ref jaegerTags.Tags,
new JaegerTag(JaegerErrorFlagTagName, JaegerTagType.BOOL, vBool: true));
}
}

string peerServiceName = null;
if (activity.Kind == ActivityKind.Client || activity.Kind == ActivityKind.Producer)
{
Expand Down Expand Up @@ -252,44 +288,6 @@ private static void ProcessJaegerTagArray(ref PooledList<JaegerTag> tags, KeyVal
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ProcessJaegerTag(ref TagEnumerationState state, string key, JaegerTag jaegerTag)
{
if (jaegerTag.VStr != null)
{
PeerServiceResolver.InspectTag(ref state, key, jaegerTag.VStr);

if (key == SpanAttributeConstants.StatusCodeKey)
{
StatusCode? statusCode = StatusHelper.GetStatusCodeForTagValue(jaegerTag.VStr);
if (statusCode == StatusCode.Error)
{
// Error flag: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk_exporters/jaeger.md#error-flag
PooledList<JaegerTag>.Add(ref state.Tags, new JaegerTag(JaegerErrorFlagTagName, JaegerTagType.BOOL, vBool: true));
}
else if (!statusCode.HasValue || statusCode == StatusCode.Unset)
{
// Unset Status is not sent: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk_exporters/jaeger.md#status
return;
}

// Normalize status since it is user-driven.
jaegerTag = new JaegerTag(key, JaegerTagType.STRING, vStr: StatusHelper.GetTagValueForStatusCode(statusCode.Value));
}
else if (key == JaegerErrorFlagTagName)
{
// Ignore `error` tag if it exists, it will be added based on StatusCode + StatusDescription.
return;
}
}
else if (jaegerTag.VLong.HasValue)
{
PeerServiceResolver.InspectTag(ref state, key, jaegerTag.VLong.Value);
}

PooledList<JaegerTag>.Add(ref state.Tags, jaegerTag);
}

private struct TagEnumerationState : IActivityEnumerator<KeyValuePair<string, object>>, PeerServiceResolver.IPeerServiceState
{
public PooledList<JaegerTag> Tags;
Expand All @@ -304,6 +302,8 @@ private struct TagEnumerationState : IActivityEnumerator<KeyValuePair<string, ob

public long Port { get; set; }

public StatusCode? StatusCode { get; set; }

public bool ForEach(KeyValuePair<string, object> activityTag)
{
if (activityTag.Value is Array)
Expand All @@ -312,7 +312,43 @@ public bool ForEach(KeyValuePair<string, object> activityTag)
}
else if (activityTag.Value != null)
{
ProcessJaegerTag(ref this, activityTag.Key, activityTag.ToJaegerTag());
var key = activityTag.Key;
var jaegerTag = activityTag.ToJaegerTag();
if (jaegerTag.VStr != null)
{
PeerServiceResolver.InspectTag(ref this, key, jaegerTag.VStr);

if (key == SpanAttributeConstants.StatusCodeKey)
{
StatusCode? statusCode = StatusHelper.GetStatusCodeForTagValue(jaegerTag.VStr);

if (statusCode == Trace.StatusCode.Error)
{
// Error flag: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk_exporters/jaeger.md#error-flag
this.StatusCode = statusCode;
return true;
}
else if (!statusCode.HasValue || statusCode == Trace.StatusCode.Unset)
{
// Unset Status is not sent: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk_exporters/jaeger.md#status
return true;
}

this.StatusCode = statusCode;
Yun-Ting marked this conversation as resolved.
Show resolved Hide resolved
return true;
}
else if (key == JaegerErrorFlagTagName)
{
// Ignore `error` tag if it exists, it will be added based on StatusCode + StatusDescription.
return true;
}
}
else if (jaegerTag.VLong.HasValue)
{
PeerServiceResolver.InspectTag(ref this, key, jaegerTag.VLong.Value);
}

PooledList<JaegerTag>.Add(ref this.Tags, jaegerTag);
}

return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,105 @@ public void JaegerActivityConverterTest_Status_ErrorFlagTest(StatusCode expected
}
}

[Theory]
[InlineData(ActivityStatusCode.Unset)]
[InlineData(ActivityStatusCode.Ok)]
[InlineData(ActivityStatusCode.Error)]
public void ToJaegerSpan_Activity_Status_And_StatusDescription_is_Set(ActivityStatusCode expectedStatusCode)
{
// Arrange
var activity = CreateTestActivity();
activity.SetStatus(expectedStatusCode);

// Act
var jaegerSpan = activity.ToJaegerSpan();

// Assert
if (expectedStatusCode == ActivityStatusCode.Unset)
{
Assert.DoesNotContain(jaegerSpan.Tags, t => t.Key == SpanAttributeConstants.StatusCodeKey);
}
else if (expectedStatusCode == ActivityStatusCode.Ok)
{
Assert.Equal("OK", jaegerSpan.Tags.FirstOrDefault(t => t.Key == SpanAttributeConstants.StatusCodeKey).VStr);
}

// expectedStatusCode is Error
else
{
Assert.Equal("ERROR", jaegerSpan.Tags.FirstOrDefault(t => t.Key == SpanAttributeConstants.StatusCodeKey).VStr);
}

if (expectedStatusCode == ActivityStatusCode.Error)
{
Assert.Contains(
jaegerSpan.Tags, t =>
t.Key == JaegerActivityExtensions.JaegerErrorFlagTagName &&
t.VType == JaegerTagType.BOOL && (t.VBool ?? false));
}
else
{
Assert.DoesNotContain(
jaegerSpan.Tags, t =>
t.Key == JaegerActivityExtensions.JaegerErrorFlagTagName);
}
}

[Fact]
public void ActivityStatus_Takes_precedence_Over_Status_Tags_ActivityStatusCodeIsOk()
{
// Arrange.
var activity = CreateTestActivity();
activity.SetStatus(ActivityStatusCode.Ok);
activity.SetTag(SpanAttributeConstants.StatusCodeKey, "ERROR");

// Enrich activity with additional tags.
activity.SetTag("myCustomTag", "myCustomTagValue");

// Act.
var jaegerSpan = activity.ToJaegerSpan();

// Assert.
Assert.Equal("OK", jaegerSpan.Tags.FirstOrDefault(t => t.Key == SpanAttributeConstants.StatusCodeKey).VStr);

Assert.Contains(jaegerSpan.Tags, t => t.Key == "otel.status_code" && t.VStr == "OK");
Assert.DoesNotContain(jaegerSpan.Tags, t => t.Key == "otel.status_code" && t.VStr == "ERROR");
Assert.DoesNotContain(jaegerSpan.Tags, t => t.Key == JaegerActivityExtensions.JaegerErrorFlagTagName);

// Ensure additional Activity tags were being converted.
Assert.Contains(jaegerSpan.Tags, t => t.Key == "myCustomTag" && t.VStr == "myCustomTagValue");
}

[Fact]
public void ActivityStatus_Takes_precedence_Over_Status_Tags_ActivityStatusCodeIsError()
{
// Arrange.
var activity = CreateTestActivity();

activity.SetStatus(ActivityStatusCode.Error);
activity.SetTag(SpanAttributeConstants.StatusCodeKey, "OK");

// Enrich activity with additional tags.
activity.SetTag("myCustomTag", "myCustomTagValue");

// Act.
var jaegerSpan = activity.ToJaegerSpan();

// Assert.
Assert.Equal("ERROR", jaegerSpan.Tags.FirstOrDefault(t => t.Key == SpanAttributeConstants.StatusCodeKey).VStr);

// ActivityStatusDescription takes higher precedence.
Assert.Equal("ERROR", jaegerSpan.Tags.FirstOrDefault(t => t.Key == SpanAttributeConstants.StatusCodeKey).VStr);

Assert.Contains(
jaegerSpan.Tags, t =>
t.Key == JaegerActivityExtensions.JaegerErrorFlagTagName &&
t.VType == JaegerTagType.BOOL && (t.VBool ?? false));

// Ensure additional Activity tags were being converted.
Assert.Contains(jaegerSpan.Tags, t => t.Key == "myCustomTag" && t.VStr == "myCustomTagValue");
}

internal static Activity CreateTestActivity(
bool setAttributes = true,
Dictionary<string, object> additionalAttributes = null,
Expand Down