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 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
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,50 @@ 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));

PooledList<JaegerTag>.Add(
ref jaegerTags.Tags,
new JaegerTag(SpanAttributeConstants.StatusDescriptionKey, JaegerTagType.STRING, vStr: activity.StatusDescription ?? string.Empty));
}
}
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));

PooledList<JaegerTag>.Add(
ref jaegerTags.Tags,
new JaegerTag(SpanAttributeConstants.StatusDescriptionKey, JaegerTagType.STRING, vStr: jaegerTags.StatusDescription ?? string.Empty));
}
}

string peerServiceName = null;
if (activity.Kind == ActivityKind.Client || activity.Kind == ActivityKind.Producer)
{
Expand Down Expand Up @@ -252,44 +296,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 +310,10 @@ private struct TagEnumerationState : IActivityEnumerator<KeyValuePair<string, ob

public long Port { get; set; }

public StatusCode? StatusCode { get; set; }

public string StatusDescription { get; set; }

public bool ForEach(KeyValuePair<string, object> activityTag)
{
if (activityTag.Value is Array)
Expand All @@ -312,7 +322,30 @@ 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);
this.StatusCode = statusCode;
Yun-Ting marked this conversation as resolved.
Show resolved Hide resolved
return true;
}
else if (key == SpanAttributeConstants.StatusDescriptionKey)
{
this.StatusDescription = jaegerTag.VStr;
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 @@ -434,15 +434,16 @@ public void JaegerActivityConverterTest_NullTagValueTest()
}

[Theory]
[InlineData(StatusCode.Unset, "unset")]
[InlineData(StatusCode.Ok, "Ok")]
[InlineData(StatusCode.Error, "ERROR")]
[InlineData(StatusCode.Unset, "iNvAlId")]
public void JaegerActivityConverterTest_Status_ErrorFlagTest(StatusCode expectedStatusCode, string statusCodeTagValue)
[InlineData(StatusCode.Unset, "unset", "")]
[InlineData(StatusCode.Ok, "Ok", "")]
[InlineData(StatusCode.Error, "ERROR", "error description")]
[InlineData(StatusCode.Unset, "iNvAlId", "")]
public void JaegerActivityConverterTest_Status_ErrorFlagTest(StatusCode expectedStatusCode, string statusCodeTagValue, string statusDescription)
{
// Arrange
var activity = CreateTestActivity();
activity.SetTag(SpanAttributeConstants.StatusCodeKey, statusCodeTagValue);
activity.SetTag(SpanAttributeConstants.StatusDescriptionKey, statusDescription);

// Act
var jaegerSpan = activity.ToJaegerSpan();
Expand All @@ -464,14 +465,156 @@ public void JaegerActivityConverterTest_Status_ErrorFlagTest(StatusCode expected

if (expectedStatusCode == StatusCode.Error)
{
Assert.Contains(jaegerSpan.Tags, t => t.Key == JaegerActivityExtensions.JaegerErrorFlagTagName && t.VType == JaegerTagType.BOOL && (t.VBool ?? false));
Assert.Contains(
jaegerSpan.Tags, t =>
t.Key == JaegerActivityExtensions.JaegerErrorFlagTagName &&
t.VType == JaegerTagType.BOOL && (t.VBool ?? false));
Assert.Contains(
jaegerSpan.Tags, t =>
t.Key == SpanAttributeConstants.StatusDescriptionKey &&
t.VType == JaegerTagType.STRING && t.VStr.Equals(statusDescription));
}
else
{
Assert.DoesNotContain(jaegerSpan.Tags, t => t.Key == JaegerActivityExtensions.JaegerErrorFlagTagName);
}
}

[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();
const string TagDescriptionOnError = "Description when TagStatusCode is Error.";
activity.SetStatus(ActivityStatusCode.Ok);
activity.SetTag(SpanAttributeConstants.StatusCodeKey, "ERROR");
activity.SetTag(SpanAttributeConstants.StatusDescriptionKey, TagDescriptionOnError);

// 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);
Assert.DoesNotContain(jaegerSpan.Tags, t => t.Key == SpanAttributeConstants.StatusDescriptionKey &&
t.VType == JaegerTagType.STRING && t.VStr.Equals(TagDescriptionOnError));

// 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();
const string StatusDescriptionOnError = "Description when ActivityStatusCode is Error.";
activity.SetStatus(ActivityStatusCode.Error, StatusDescriptionOnError);
activity.SetTag(SpanAttributeConstants.StatusCodeKey, "OK");

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

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

// Assert.
Assert.Contains(jaegerSpan.Tags, t => t.Key == "otel.status_code" && t.VStr == "ERROR");
Assert.Contains(jaegerSpan.Tags, t => t.Key == JaegerActivityExtensions.JaegerErrorFlagTagName);
Assert.Contains(jaegerSpan.Tags, t => t.Key == SpanAttributeConstants.StatusDescriptionKey &&
t.VType == JaegerTagType.STRING && t.VStr.Equals(StatusDescriptionOnError));

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

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

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

const string StatusDescriptionOnError = "Description when ActivityStatusCode is Error.";
const string TagDescriptionOnError = "Description when TagStatusCode is Error.";
activity.SetStatus(ActivityStatusCode.Error, StatusDescriptionOnError);
activity.SetTag(SpanAttributeConstants.StatusCodeKey, "ERROR");
activity.SetTag(SpanAttributeConstants.StatusDescriptionKey, TagDescriptionOnError);

// 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);

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

Assert.Contains(
jaegerSpan.Tags, t =>
t.Key == SpanAttributeConstants.StatusDescriptionKey &&
t.VType == JaegerTagType.STRING && t.VStr.Equals(StatusDescriptionOnError));

// 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