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

Support for Activity Status and StatusDescription via switch #2605

Closed
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
18 changes: 18 additions & 0 deletions src/OpenTelemetry.Api/Internal/StatusHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// </copyright>

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using OpenTelemetry.Trace;

Expand Down Expand Up @@ -59,5 +60,22 @@ string _ when OkStatusCodeTagValue.Equals(statusCodeTagValue, StringComparison.O
_ => (StatusCode?)null,
};
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ActivityStatusCode GetActivityStatusCodeForTagValue(string statusCodeTagValue)
{
return statusCodeTagValue switch
{
/*
* Note: Order here does matter for perf. Unset is
* first because assumption is most spans will be
* Unset, then Error. Ok is not set by the SDK.
*/
string _ when UnsetStatusCodeTagValue.Equals(statusCodeTagValue, StringComparison.OrdinalIgnoreCase) => ActivityStatusCode.Unset,
string _ when ErrorStatusCodeTagValue.Equals(statusCodeTagValue, StringComparison.OrdinalIgnoreCase) => ActivityStatusCode.Error,
string _ when OkStatusCodeTagValue.Equals(statusCodeTagValue, StringComparison.OrdinalIgnoreCase) => ActivityStatusCode.Ok,
_ => ActivityStatusCode.Unset,
};
}
}
}
11 changes: 11 additions & 0 deletions src/OpenTelemetry.Exporter.Console/ConsoleActivityExporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ public override ExportResult Export(in Batch<Activity> batch)
this.WriteLine($"Activity.Kind: {activity.Kind}");
this.WriteLine($"Activity.StartTime: {activity.StartTimeUtc:yyyy-MM-ddTHH:mm:ss.fffffffZ}");
this.WriteLine($"Activity.Duration: {activity.Duration}");

if (activity.Status != ActivityStatusCode.Unset)
{
this.WriteLine($"Activity.Status: {activity.Status}");
}

if (activity.StatusDescription != null)
{
this.WriteLine($"Activity.StatusDescription: {activity.StatusDescription}");
}

if (activity.TagObjects.Any())
{
this.WriteLine("Activity.TagObjects:");
Expand Down
3 changes: 3 additions & 0 deletions src/OpenTelemetry/.publicApi/net461/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
abstract OpenTelemetry.Metrics.MetricReader.ProcessMetrics(in OpenTelemetry.Batch<OpenTelemetry.Metrics.Metric> metrics, int timeoutMilliseconds) -> bool
OpenTelemetry.BackwardCompatibilitySwitches
OpenTelemetry.BaseExporter<T>.ForceFlush(int timeoutMilliseconds = -1) -> bool
OpenTelemetry.Batch<T>.Batch(T[] items, int count) -> void
OpenTelemetry.Batch<T>.Count.get -> long
Expand Down Expand Up @@ -107,6 +108,8 @@ override OpenTelemetry.Metrics.MeterProviderBuilderBase.AddMeter(params string[]
override OpenTelemetry.Metrics.PeriodicExportingMetricReader.Dispose(bool disposing) -> void
override OpenTelemetry.Metrics.PeriodicExportingMetricReader.OnShutdown(int timeoutMilliseconds) -> bool
readonly OpenTelemetry.Metrics.BaseExportingMetricReader.exporter -> OpenTelemetry.BaseExporter<OpenTelemetry.Metrics.Metric>
static OpenTelemetry.BackwardCompatibilitySwitches.StatusTagMigrationEnabled.get -> bool
static OpenTelemetry.BackwardCompatibilitySwitches.StatusTagMigrationEnabled.set -> void
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddReader(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, OpenTelemetry.Metrics.MetricReader reader) -> OpenTelemetry.Metrics.MeterProviderBuilder
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddView(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, string instrumentName, OpenTelemetry.Metrics.MetricStreamConfiguration metricStreamConfiguration) -> OpenTelemetry.Metrics.MeterProviderBuilder
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddView(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, string instrumentName, string name) -> OpenTelemetry.Metrics.MeterProviderBuilder
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
abstract OpenTelemetry.Metrics.MetricReader.ProcessMetrics(in OpenTelemetry.Batch<OpenTelemetry.Metrics.Metric> metrics, int timeoutMilliseconds) -> bool
OpenTelemetry.BackwardCompatibilitySwitches
OpenTelemetry.BaseExporter<T>.ForceFlush(int timeoutMilliseconds = -1) -> bool
OpenTelemetry.Batch<T>.Batch(T[] items, int count) -> void
OpenTelemetry.Batch<T>.Count.get -> long
Expand Down Expand Up @@ -107,6 +108,8 @@ override OpenTelemetry.Metrics.MeterProviderBuilderBase.AddMeter(params string[]
override OpenTelemetry.Metrics.PeriodicExportingMetricReader.Dispose(bool disposing) -> void
override OpenTelemetry.Metrics.PeriodicExportingMetricReader.OnShutdown(int timeoutMilliseconds) -> bool
readonly OpenTelemetry.Metrics.BaseExportingMetricReader.exporter -> OpenTelemetry.BaseExporter<OpenTelemetry.Metrics.Metric>
static OpenTelemetry.BackwardCompatibilitySwitches.StatusTagMigrationEnabled.get -> bool
static OpenTelemetry.BackwardCompatibilitySwitches.StatusTagMigrationEnabled.set -> void
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddReader(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, OpenTelemetry.Metrics.MetricReader reader) -> OpenTelemetry.Metrics.MeterProviderBuilder
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddView(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, string instrumentName, OpenTelemetry.Metrics.MetricStreamConfiguration metricStreamConfiguration) -> OpenTelemetry.Metrics.MeterProviderBuilder
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddView(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, string instrumentName, string name) -> OpenTelemetry.Metrics.MeterProviderBuilder
Expand Down
89 changes: 89 additions & 0 deletions src/OpenTelemetry/BackwardCompatibilityHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// <copyright file="BackwardCompatibilityHelper.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using System.Collections.Generic;
using System.Diagnostics;
using OpenTelemetry.Internal;
using OpenTelemetry.Trace;

namespace OpenTelemetry
{
internal static class BackwardCompatibilityHelper
{
internal static void SetActivityStatusUsingTags(Activity activity)
{
var tagState = default(TagEnumerationState);

activity.EnumerateTags(ref tagState);

if (tagState.StatusCode == ActivityStatusCode.Ok)
{
activity.SetStatus(tagState.StatusCode);
}
else if (tagState.StatusCode == ActivityStatusCode.Error)
{
activity.SetStatus(tagState.StatusCode, tagState.StatusDescription);
}
}

internal struct TagEnumerationState : IActivityEnumerator<KeyValuePair<string, object>>
{
public ActivityStatusCode StatusCode { get; set; }

public string StatusDescription { get; set; }

public bool ForEach(KeyValuePair<string, object> activityTag)
{
if (activityTag.Value == null)
{
return true;
}

string key = activityTag.Key;

if (activityTag.Value is string strVal)
{
if (key == SpanAttributeConstants.StatusCodeKey)
{
this.StatusCode = StatusHelper.GetActivityStatusCodeForTagValue(strVal);
if (this.StatusCode != ActivityStatusCode.Error)
{
// Description is only valid for Error
// No need to look further for description.
return false;
}

return true;
vishweshbankwar marked this conversation as resolved.
Show resolved Hide resolved
}
else if (key == SpanAttributeConstants.StatusDescriptionKey)
{
this.StatusDescription = strVal;
if (this.StatusCode != ActivityStatusCode.Unset)
{
// We now have both Status and StatusDescription
// No need to look further for other tags.
return false;
}

return true;
vishweshbankwar marked this conversation as resolved.
Show resolved Hide resolved
}
}

return true;
}
}
}
}
32 changes: 32 additions & 0 deletions src/OpenTelemetry/BackwardCompatibilitySwitches.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// <copyright file="BackwardCompatibilitySwitches.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using System.Diagnostics;

namespace OpenTelemetry
{
public static class BackwardCompatibilitySwitches
{
/// <summary>
/// Gets or sets a value indicating whether or not activity status migration is enabled. Default value: true.
/// </summary>
/// <remarks>
/// If true then <see cref="Activity.Status"/> and <see cref="Activity.StatusDescription"/> properties (added in .NET 6) will be set
/// from `otel.status_code` and `otel.status_description` tag values respectively prior to export.
/// </remarks>
public static bool StatusTagMigrationEnabled { get; set; } = true;
}
}
11 changes: 11 additions & 0 deletions src/OpenTelemetry/BaseExportProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// </copyright>

using System;
using System.Diagnostics;
using OpenTelemetry.Internal;

namespace OpenTelemetry
Expand Down Expand Up @@ -47,6 +48,16 @@ public sealed override void OnStart(T data)

public override void OnEnd(T data)
{
var activity = data as Activity;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will add unwanted perf impact, when using LogRecord.
I think we need to add BaseActivityExportProcessor, which inherits BaseExportProcessor<Activity>.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be same as SimpleActivityExportProcessor and BatchActivityExportProcessor right?
The reason we wanted to put it here is to cover cases where exporters are inheriting from BaseExportProcessor and not overriding OnEnd.

Instead of introducing new one - should we add this check in Batch and Simple Activity processor (this is what I had before).


if (activity != null)
{
if (BackwardCompatibilitySwitches.StatusTagMigrationEnabled && activity.Status == ActivityStatusCode.Unset)
{
BackwardCompatibilityHelper.SetActivityStatusUsingTags(activity);
}
}

this.OnExport(data);
}

Expand Down
2 changes: 1 addition & 1 deletion src/OpenTelemetry/BatchActivityExportProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public override void OnEnd(Activity data)
return;
}

this.OnExport(data);
base.OnEnd(data);
}
}
}
3 changes: 3 additions & 0 deletions src/OpenTelemetry/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

* Set activity `Status` and `StatusDescription` using status tags.
([2605](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2605))

## 1.2.0-beta2

Released 2021-Nov-19
Expand Down
2 changes: 1 addition & 1 deletion src/OpenTelemetry/SimpleActivityExportProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public override void OnEnd(Activity data)
return;
}

this.OnExport(data);
base.OnEnd(data);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@
using System.Diagnostics;
using System.Threading;
using OpenTelemetry.Exporter;
using OpenTelemetry.Internal;
using Xunit;

namespace OpenTelemetry.Trace.Tests
{
public class BatchExportActivityProcessorTest
{
private const string ActivitySourceName = "BatchActivityExportProcessorTest";

[Fact]
public void CheckNullExporter()
{
Expand Down Expand Up @@ -196,5 +199,50 @@ public void CheckExportDrainsBatchOnFailure()

Assert.Equal(3, processor.ProcessedCount); // Verify batch was drained even though nothing was exported.
}

[Theory]
[InlineData("OK", null, true)]
[InlineData("ERROR", "Error Description", true)]
[InlineData("OK", null, false)]
[InlineData("ERROR", "Error Description", false)]
public void ActivityStatusIsSetIfStatusMigrationIsEnabled(string statusCode, string statusDescription, bool isStatusMigrationEnabled)
{
var sampler = new AlwaysOnSampler();
var exportedItems = new List<Activity>();
var processor = new BatchActivityExportProcessor(new InMemoryExporter<Activity>(exportedItems));
using var activitySource = new ActivitySource(ActivitySourceName);

// Set status migration - it is enabled by default.
BackwardCompatibilitySwitches.StatusTagMigrationEnabled = isStatusMigrationEnabled;
using var sdk = Sdk.CreateTracerProviderBuilder()
.AddSource(ActivitySourceName)
.SetSampler(sampler)
.AddProcessor(processor)
.Build();

using (var activity = activitySource.StartActivity("Activity"))
{
activity?.SetTag("otel.status_code", statusCode);
if (statusDescription != null)
{
activity?.SetTag("otel.status_description", statusDescription);
}
}

processor.ForceFlush();
ActivityStatusCode expectedStatusForTagValue = StatusHelper.GetActivityStatusCodeForTagValue(statusCode);

if (isStatusMigrationEnabled)
{
Assert.Equal(expectedStatusForTagValue, exportedItems[0].Status);
Assert.Equal(statusDescription, exportedItems[0].StatusDescription);
}
else
{
Assert.Equal(ActivityStatusCode.Unset, exportedItems[0].Status);
Assert.NotEqual(expectedStatusForTagValue, exportedItems[0].Status);
Assert.Null(exportedItems[0].StatusDescription);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@
using System.Diagnostics;
using System.Threading;
using OpenTelemetry.Exporter;
using OpenTelemetry.Internal;
using Xunit;

namespace OpenTelemetry.Trace.Tests
{
public class SimpleExportActivityProcessorTest
{
private const string ActivitySourceName = "SimpleActivityExportProcessorTest";

[Fact]
public void CheckNullExporter()
{
Expand Down Expand Up @@ -113,5 +116,49 @@ public void CheckExportForRecordingButNotSampledActivity()
processor.OnEnd(activity);
Assert.Empty(exportedItems);
}

[Theory]
[InlineData("OK", null, true)]
[InlineData("ERROR", "Error Description", true)]
[InlineData("OK", null, false)]
[InlineData("ERROR", "Error Description", false)]
public void ActivityStatusIsSetIfStatusMigrationIsEnabled(string statusCode, string statusDescription, bool isStatusMigrationEnabled)
{
var sampler = new AlwaysOnSampler();
var exportedItems = new List<Activity>();
var processor = new SimpleActivityExportProcessor(new InMemoryExporter<Activity>(exportedItems));
using var activitySource = new ActivitySource(ActivitySourceName);

// Set status migration - it is true by default.
BackwardCompatibilitySwitches.StatusTagMigrationEnabled = isStatusMigrationEnabled;
using var sdk = Sdk.CreateTracerProviderBuilder()
.AddSource(ActivitySourceName)
.SetSampler(sampler)
.AddProcessor(processor)
.Build();

using (var activity = activitySource.StartActivity("Activity"))
{
activity?.SetTag("otel.status_code", statusCode);
if (statusDescription != null)
{
activity?.SetTag("otel.status_description", statusDescription);
}
}

ActivityStatusCode expectedStatusForTagValue = StatusHelper.GetActivityStatusCodeForTagValue(statusCode);

if (isStatusMigrationEnabled)
{
Assert.Equal(expectedStatusForTagValue, exportedItems[0].Status);
Assert.Equal(statusDescription, exportedItems[0].StatusDescription);
}
else
{
Assert.Equal(ActivityStatusCode.Unset, exportedItems[0].Status);
Assert.NotEqual(expectedStatusForTagValue, exportedItems[0].Status);
Assert.Null(exportedItems[0].StatusDescription);
}
}
}
}