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

[Part3] Support Activity Status and status description in OTLP Exporter. #3100

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

## Unreleased

* Added support for Activity Status and StatusDescription which were
cijothomas marked this conversation as resolved.
Show resolved Hide resolved
added to Activity from `System.Diagnostics.DiagnosticSource` version 6.0.
Prior to version 6.0, setting the status of an Activity was provided by the
.NET OpenTelemetry API via the `Activity.SetStatus` extension method in the
`OpenTelemetry.Trace` namespace. Internally, this extension method added the
status as tags on the Activity: `otel.status_code` and `otel.status_description`.
Therefore, to maintain backward compatibility, the exporter falls back to using
these tags to infer status.
([#3100](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3100))

* Fixed OTLP metric exporter to default to a periodic 60 second export cycle.
A bug was introduced in #2717 that caused the OTLP metric export to default
to a manual export cycle (i.e., requiring an explicit flush). A workaround
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ internal static OtlpTrace.Span ToOtlpSpan(this Activity activity)
otlpTags.Tags.Return();
}

otlpSpan.Status = ToOtlpStatus(ref otlpTags);
otlpSpan.Status = activity.ToOtlpStatus(ref otlpTags);

EventEnumerationState otlpEvents = default;
activity.EnumerateEvents(ref otlpEvents);
Expand Down Expand Up @@ -237,21 +237,39 @@ internal static OtlpCommon.KeyValue ToOtlpAttribute(this KeyValuePair<string, ob
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static OtlpTrace.Status ToOtlpStatus(ref TagEnumerationState otlpTags)
private static OtlpTrace.Status ToOtlpStatus(this Activity activity, ref TagEnumerationState otlpTags)
cijothomas marked this conversation as resolved.
Show resolved Hide resolved
{
var status = StatusHelper.GetStatusCodeForTagValue(otlpTags.StatusCode);

if (!status.HasValue)
if (activity.Status == ActivityStatusCode.Unset && StatusHelper.GetStatusCodeForTagValue(otlpTags.StatusCode) == null)
{
return null;
}

var otlpStatus = new OtlpTrace.Status
OtlpTrace.Status.Types.StatusCode otlpActivityStatusCode = OtlpTrace.Status.Types.StatusCode.Unset;
string otlpStatusDescription = null;
if (activity.Status != ActivityStatusCode.Unset)
{
// The numerical values of the two enumerations match, a simple cast is enough.
Code = (OtlpTrace.Status.Types.StatusCode)(int)status,
};
otlpActivityStatusCode = (OtlpTrace.Status.Types.StatusCode)(int)activity.Status;
if (!string.IsNullOrEmpty(activity.StatusDescription))
{
otlpStatusDescription = activity.StatusDescription;
}
}
else
{
var statusCode = StatusHelper.GetStatusCodeForTagValue(otlpTags.StatusCode);
Yun-Ting marked this conversation as resolved.
Show resolved Hide resolved
if (statusCode != StatusCode.Unset)
Yun-Ting marked this conversation as resolved.
Show resolved Hide resolved
{
// The numerical values of the two enumerations match, a simple cast is enough.
otlpActivityStatusCode = (OtlpTrace.Status.Types.StatusCode)(int)statusCode;
if (!string.IsNullOrEmpty(otlpTags.StatusDescription))
{
otlpStatusDescription = otlpTags.StatusDescription;
}
}
}

var otlpStatus = new OtlpTrace.Status { Code = otlpActivityStatusCode };
if (otlpStatus.Code != OtlpTrace.Status.Types.StatusCode.Error)
{
#pragma warning disable CS0612 // Type or member is obsolete
Expand All @@ -265,9 +283,9 @@ private static OtlpTrace.Status ToOtlpStatus(ref TagEnumerationState otlpTags)
#pragma warning restore CS0612 // Type or member is obsolete
}

if (!string.IsNullOrEmpty(otlpTags.StatusDescription))
if (!string.IsNullOrEmpty(otlpStatusDescription))
{
otlpStatus.Message = otlpTags.StatusDescription;
otlpStatus.Message = otlpStatusDescription;
}

return otlpStatus;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,97 @@ public void ToOtlpSpanActivitiesWithNullArrayTest()
Assert.Null(stringArray[2].Value);
}

[Theory]
[InlineData(ActivityStatusCode.Unset, "")]
[InlineData(ActivityStatusCode.Ok, "")]
[InlineData(ActivityStatusCode.Error, "error description")]
public void ToOtlpSpanNativeActivityStatusTest(ActivityStatusCode expectedStatusCode, string statusDescription)
{
using var activitySource = new ActivitySource(nameof(this.ToOtlpSpanTest));
using var activity = activitySource.StartActivity("Name");
activity.SetStatus(expectedStatusCode, statusDescription);

var otlpSpan = activity.ToOtlpSpan();

if (expectedStatusCode == ActivityStatusCode.Unset)
{
Assert.Null(otlpSpan.Status);
}
else
{
Assert.NotNull(otlpSpan.Status);
Assert.Equal((int)expectedStatusCode, (int)otlpSpan.Status.Code);
if (expectedStatusCode == ActivityStatusCode.Error)
{
Assert.Equal(statusDescription, otlpSpan.Status.Message);
}

if (expectedStatusCode == ActivityStatusCode.Ok)
{
Assert.Empty(otlpSpan.Status.Message);
}
}
}

[Theory]
[InlineData(StatusCode.Unset, "unset", "")]
[InlineData(StatusCode.Ok, "Ok", "")]
[InlineData(StatusCode.Error, "ERROR", "error description")]
Yun-Ting marked this conversation as resolved.
Show resolved Hide resolved
public void ToOtlpSpanStatusTagTest(StatusCode expectedStatusCode, string statusCodeTagValue, string statusDescription)
{
using var activitySource = new ActivitySource(nameof(this.ToOtlpSpanTest));
using var activity = activitySource.StartActivity("Name");
activity.SetTag(SpanAttributeConstants.StatusCodeKey, statusCodeTagValue);
activity.SetTag(SpanAttributeConstants.StatusDescriptionKey, statusDescription);

var otlpSpan = activity.ToOtlpSpan();

Assert.NotNull(otlpSpan.Status);
Assert.Equal((int)expectedStatusCode, (int)otlpSpan.Status.Code);

if (expectedStatusCode == StatusCode.Error)
{
Assert.Equal(statusDescription, otlpSpan.Status.Message);
}
else
{
Assert.Empty(otlpSpan.Status.Message);
}
}

[Fact]
public void ToOtlpSpanActivityStatusTakesPrecedenceOverStatusTagsWhenActivityStatusCodeIsOk()
{
using var activitySource = new ActivitySource(nameof(this.ToOtlpSpanTest));
using var activity = activitySource.StartActivity("Name");
const string TagDescriptionOnError = "Description when TagStatusCode is Error.";
activity.SetStatus(ActivityStatusCode.Ok);
activity.SetTag(SpanAttributeConstants.StatusCodeKey, "ERROR");
activity.SetTag(SpanAttributeConstants.StatusDescriptionKey, TagDescriptionOnError);

var otlpSpan = activity.ToOtlpSpan();

Assert.NotNull(otlpSpan.Status);
Assert.Equal((int)ActivityStatusCode.Ok, (int)otlpSpan.Status.Code);
Assert.Empty(otlpSpan.Status.Message);
}

[Fact]
public void ToOtlpSpanActivityStatusTakesPrecedenceOverStatusTagsWhenActivityStatusCodeIsError()
{
using var activitySource = new ActivitySource(nameof(this.ToOtlpSpanTest));
using var activity = activitySource.StartActivity("Name");
const string StatusDescriptionOnError = "Description when ActivityStatusCode is Error.";
activity.SetStatus(ActivityStatusCode.Error, StatusDescriptionOnError);
activity.SetTag(SpanAttributeConstants.StatusCodeKey, "OK");

var otlpSpan = activity.ToOtlpSpan();

Assert.NotNull(otlpSpan.Status);
Assert.Equal((int)ActivityStatusCode.Error, (int)otlpSpan.Status.Code);
Assert.Equal(StatusDescriptionOnError, otlpSpan.Status.Message);
}

[Fact]
public void ToOtlpSpanPeerServiceTest()
{
Expand Down