Skip to content

Commit

Permalink
[Part3] Support Activity Status and status description in OTLP Export…
Browse files Browse the repository at this point in the history
…er. (#3100)
  • Loading branch information
Yun-Ting authored Mar 29, 2022
1 parent bea9b60 commit add7eee
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 10 deletions.
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
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,24 +237,42 @@ 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)
{
var status = StatusHelper.GetStatusCodeForTagValue(otlpTags.StatusCode);

if (!status.HasValue)
var statusCodeForTagValue = StatusHelper.GetStatusCodeForTagValue(otlpTags.StatusCode);
if (activity.Status == ActivityStatusCode.Unset && statusCodeForTagValue == 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 (activity.Status == ActivityStatusCode.Error && !string.IsNullOrEmpty(activity.StatusDescription))
{
otlpStatusDescription = activity.StatusDescription;
}
}
else
{
if (statusCodeForTagValue != StatusCode.Unset)
{
// The numerical values of the two enumerations match, a simple cast is enough.
otlpActivityStatusCode = (OtlpTrace.Status.Types.StatusCode)(int)statusCodeForTagValue;
if (statusCodeForTagValue == StatusCode.Error && !string.IsNullOrEmpty(otlpTags.StatusDescription))
{
otlpStatusDescription = otlpTags.StatusDescription;
}
}
}

if (!string.IsNullOrEmpty(otlpTags.StatusDescription))
var otlpStatus = new OtlpTrace.Status { Code = otlpActivityStatusCode };
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,113 @@ public void ToOtlpSpanActivitiesWithNullArrayTest()
Assert.Null(stringArray[2].Value);
}

[Theory]
[InlineData(ActivityStatusCode.Unset, "Description will be ingored if status is Unset.")]
[InlineData(ActivityStatusCode.Ok, "Description will be ingored if status is Okay.")]
[InlineData(ActivityStatusCode.Error, "Description will be kept if status is Error.")]
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", "Description will be ingored if status is Unset.")]
[InlineData(StatusCode.Ok, "Ok", "Description must only be used with the Error StatusCode.")]
[InlineData(StatusCode.Error, "Error", "Error description.")]
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);
}
}

[Theory]
[InlineData(StatusCode.Unset, "uNsET")]
[InlineData(StatusCode.Ok, "oK")]
[InlineData(StatusCode.Error, "ERROR")]
public void ToOtlpSpanStatusTagIsCaseInsensitiveTest(StatusCode expectedStatusCode, string statusCodeTagValue)
{
using var activitySource = new ActivitySource(nameof(this.ToOtlpSpanTest));
using var activity = activitySource.StartActivity("Name");
activity.SetTag(SpanAttributeConstants.StatusCodeKey, statusCodeTagValue);

var otlpSpan = activity.ToOtlpSpan();

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

[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

0 comments on commit add7eee

Please sign in to comment.