Skip to content

Commit

Permalink
[Part1] Support Activity Status and status description in Zipkin Expo…
Browse files Browse the repository at this point in the history
…rter. (#3003)
  • Loading branch information
Yun-Ting authored Mar 22, 2022
1 parent bdcf942 commit 96b043e
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 11 deletions.
6 changes: 6 additions & 0 deletions src/OpenTelemetry.Exporter.Zipkin/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 special tags "otel.status_code", "otel.status_description".
([#3003](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3003))

## 1.2.0-rc3

Released 2022-Mar-04
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,48 @@ internal static ZipkinSpan ToZipkinSpan(this Activity activity, ZipkinEndpoint l

activity.EnumerateTags(ref tagState);

// When status is set on Activity using the native Status field in activity,
// which was first introduced in System.Diagnostic.DiagnosticSource 6.0.0.
if (activity.Status != ActivityStatusCode.Unset)
{
if (activity.Status == ActivityStatusCode.Ok)
{
PooledList<KeyValuePair<string, object>>.Add(
ref tagState.Tags,
new KeyValuePair<string, object>(
SpanAttributeConstants.StatusCodeKey,
"OK"));
}

// activity.Status is Error
else
{
PooledList<KeyValuePair<string, object>>.Add(
ref tagState.Tags,
new KeyValuePair<string, object>(
SpanAttributeConstants.StatusCodeKey,
"ERROR"));

// Error flag rule from https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk_exporters/zipkin.md#status
PooledList<KeyValuePair<string, object>>.Add(
ref tagState.Tags,
new KeyValuePair<string, object>(
ZipkinErrorFlagTagName,
activity.StatusDescription ?? string.Empty));
}
}

// In the case when both activity status and status tag were set,
// activity status takes precedence over status tag.
else if (tagState.StatusCode.HasValue && tagState.StatusCode != StatusCode.Unset)
{
PooledList<KeyValuePair<string, object>>.Add(
ref tagState.Tags,
new KeyValuePair<string, object>(
SpanAttributeConstants.StatusCodeKey,
StatusHelper.GetTagValueForStatusCode(tagState.StatusCode.Value)));
}

var activitySource = activity.Source;
if (!string.IsNullOrEmpty(activitySource.Name))
{
Expand All @@ -72,9 +114,9 @@ internal static ZipkinSpan ToZipkinSpan(this Activity activity, ZipkinEndpoint l
}
}

if (tagState.StatusCode == StatusCode.Error)
if (activity.Status == ActivityStatusCode.Unset && tagState.StatusCode.HasValue && tagState.StatusCode == StatusCode.Error)
{
// Error flag rule from https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk_exporters/zipkin.md#status
// Error flag rule from https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk_exporters/
PooledList<KeyValuePair<string, object>>.Add(
ref tagState.Tags,
new KeyValuePair<string, object>(
Expand Down Expand Up @@ -185,15 +227,7 @@ public bool ForEach(KeyValuePair<string, object> activityTag)
if (key == SpanAttributeConstants.StatusCodeKey)
{
this.StatusCode = StatusHelper.GetStatusCodeForTagValue(strVal);

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

// Normalize status since it is user-driven.
activityTag = new KeyValuePair<string, object>(key, StatusHelper.GetTagValueForStatusCode(this.StatusCode.Value));
return true;
}
else if (key == SpanAttributeConstants.StatusDescriptionKey)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// limitations under the License.
// </copyright>

using System.Diagnostics;
using System.Linq;
using OpenTelemetry.Exporter.Zipkin.Tests;
using OpenTelemetry.Internal;
Expand Down Expand Up @@ -125,5 +126,145 @@ public void ToZipkinSpan_Status_ErrorFlagTest(StatusCode expectedStatusCode, str
Assert.DoesNotContain(zipkinSpan.Tags, t => t.Key == "error");
}
}

[Theory]
[InlineData(ActivityStatusCode.Unset)]
[InlineData(ActivityStatusCode.Ok)]
[InlineData(ActivityStatusCode.Error)]
public void ToZipkinSpan_Activity_Status_And_StatusDescription_is_Set(ActivityStatusCode expectedStatusCode)
{
// Arrange.
const string description = "Description when ActivityStatusCode is Error.";
var activity = ZipkinExporterTests.CreateTestActivity();
activity.SetStatus(expectedStatusCode, description);

// Act.
var zipkinSpan = activity.ToZipkinSpan(DefaultZipkinEndpoint);

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

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

if (expectedStatusCode == ActivityStatusCode.Error)
{
Assert.Contains(
zipkinSpan.Tags, t =>
t.Key == ZipkinActivityConversionExtensions.ZipkinErrorFlagTagName &&
(string)t.Value == description);
}
else
{
Assert.DoesNotContain(
zipkinSpan.Tags, t =>
t.Key == ZipkinActivityConversionExtensions.ZipkinErrorFlagTagName);
}
}

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

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

// Act.
var zipkinSpan = activity.ToZipkinSpan(DefaultZipkinEndpoint);

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

Assert.Contains(zipkinSpan.Tags, t => t.Key == "otel.status_code" && (string)t.Value == "OK");
Assert.DoesNotContain(zipkinSpan.Tags, t => t.Key == "otel.status_code" && (string)t.Value == "ERROR");

// Ensure additional Activity tags were being converted.
Assert.Contains(zipkinSpan.Tags, t => t.Key == "myCustomTag" && (string)t.Value == "myCustomTagValue");
Assert.DoesNotContain(zipkinSpan.Tags, t => t.Key == ZipkinActivityConversionExtensions.ZipkinErrorFlagTagName);
}

[Fact]
public void ActivityStatus_Takes_precedence_Over_Status_Tags_ActivityStatusCodeIsError()
{
// Arrange.
var activity = ZipkinExporterTests.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 zipkinSpan = activity.ToZipkinSpan(DefaultZipkinEndpoint);

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

// ActivityStatusDescription takes higher precedence.
Assert.Contains(
zipkinSpan.Tags, t =>
t.Key == ZipkinActivityConversionExtensions.ZipkinErrorFlagTagName &&
(string)t.Value == StatusDescriptionOnError);
Assert.DoesNotContain(
zipkinSpan.Tags, t =>
t.Key == ZipkinActivityConversionExtensions.ZipkinErrorFlagTagName &&
(string)t.Value == TagDescriptionOnError);

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

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

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

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

// Act.
var zipkinSpan = activity.ToZipkinSpan(DefaultZipkinEndpoint);

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

// ActivityStatusDescription takes higher precedence.
Assert.Contains(
zipkinSpan.Tags, t =>
t.Key == ZipkinActivityConversionExtensions.ZipkinErrorFlagTagName &&
(string)t.Value == StatusDescriptionOnError);
Assert.DoesNotContain(
zipkinSpan.Tags, t =>
t.Key == ZipkinActivityConversionExtensions.ZipkinErrorFlagTagName &&
(string)t.Value == TagDescriptionOnError);

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

0 comments on commit 96b043e

Please sign in to comment.