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

[Part1] Support Activity Status and status description in Zipkin Exporter. #3003

Merged
merged 33 commits into from
Mar 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
1ab2baa
initial commit
Yun-Ting Mar 9, 2022
573bcdf
Merge branch 'yunl/status' of https://github.com/Yun-Ting/opentelemet…
Yun-Ting Mar 9, 2022
f7c6482
Revert "initial commit"
Yun-Ting Mar 9, 2022
29c17bc
Revert "Revert "initial commit""
Yun-Ting Mar 9, 2022
2539d45
organize
Yun-Ting Mar 9, 2022
902d2ba
revert unrelated change
Yun-Ting Mar 10, 2022
8f8ca9e
changelog
Yun-Ting Mar 10, 2022
e708247
additional tags will not be dropped
Yun-Ting Mar 10, 2022
751fd0e
fix comment
Yun-Ting Mar 10, 2022
53733c3
fix integration test; tag order matters
Yun-Ting Mar 10, 2022
fddecd0
comment and nit
Yun-Ting Mar 11, 2022
91f83a9
bugs
Yun-Ting Mar 11, 2022
d934786
fix port
Yun-Ting Mar 11, 2022
3bb525b
comment, integration test, reorder
Yun-Ting Mar 11, 2022
0248d74
fix AV
Yun-Ting Mar 14, 2022
18593f5
Update src/OpenTelemetry.Api/CHANGELOG.md
Yun-Ting Mar 14, 2022
d0ceec8
Merge branch 'yunl/status' of https://github.com/Yun-Ting/opentelemet…
Yun-Ting Mar 14, 2022
af8835c
comment
Yun-Ting Mar 14, 2022
03094e5
Merge branch 'main' into yunl/status
Yun-Ting Mar 17, 2022
b0a0077
changelog
Yun-Ting Mar 17, 2022
ab085c8
comment on a comment
Yun-Ting Mar 17, 2022
b8bfa7a
testcase
Yun-Ting Mar 17, 2022
2901813
Merge branch 'main' into yunl/status
cijothomas Mar 17, 2022
3a4b310
comment
Yun-Ting Mar 18, 2022
cc46c97
Merge branch 'yunl/status' of https://github.com/Yun-Ting/opentelemet…
Yun-Ting Mar 18, 2022
9086968
changelog
Yun-Ting Mar 21, 2022
d009201
sacrificing perf for easier reviewer experience by not changing the t…
Yun-Ting Mar 21, 2022
380f514
fix error precedence
Yun-Ting Mar 21, 2022
900e84f
Merge branch 'main' into yunl/status
cijothomas Mar 22, 2022
ff54ea2
comment
Yun-Ting Mar 22, 2022
ac081d7
Merge branch 'yunl/status' of https://github.com/Yun-Ting/opentelemet…
Yun-Ting Mar 22, 2022
3216b16
trigger CI
Yun-Ting Mar 22, 2022
578d445
Update src/OpenTelemetry.Exporter.Zipkin/CHANGELOG.md
Yun-Ting Mar 22, 2022
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.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)
Yun-Ting marked this conversation as resolved.
Show resolved Hide resolved
{
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(
Yun-Ting marked this conversation as resolved.
Show resolved Hide resolved
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);
Yun-Ting marked this conversation as resolved.
Show resolved Hide resolved
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");
Yun-Ting marked this conversation as resolved.
Show resolved Hide resolved
cijothomas marked this conversation as resolved.
Show resolved Hide resolved
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");
}
}
}