diff --git a/src/OpenTelemetry.Api/CHANGELOG.md b/src/OpenTelemetry.Api/CHANGELOG.md
index 1d9374526..2a2508082 100644
--- a/src/OpenTelemetry.Api/CHANGELOG.md
+++ b/src/OpenTelemetry.Api/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+* Added `RecordException` in `TelemetrySpan`
+ ([#1116](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1116))
* `PropagationContext` is now used instead of `ActivityContext` in the
`ITextFormat` API
([#1048](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1048))
diff --git a/src/OpenTelemetry.Api/Trace/ActivityExtensions.cs b/src/OpenTelemetry.Api/Trace/ActivityExtensions.cs
index 70e1a1ed4..0e9a82512 100644
--- a/src/OpenTelemetry.Api/Trace/ActivityExtensions.cs
+++ b/src/OpenTelemetry.Api/Trace/ActivityExtensions.cs
@@ -19,6 +19,7 @@ using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
+using OpenTelemetry.Internal;
namespace OpenTelemetry.Trace
{
@@ -84,6 +85,33 @@ namespace OpenTelemetry.Trace
SetKindProperty(activity, kind);
}
+ ///
+ /// Record Exception.
+ ///
+ /// Activity instance.
+ /// Exception to be recorded.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void RecordException(this Activity activity, Exception ex)
+ {
+ if (ex == null)
+ {
+ return;
+ }
+
+ var tagsCollection = new ActivityTagsCollection
+ {
+ { SemanticConventions.AttributeExceptionType, ex.GetType().Name },
+ { SemanticConventions.AttributeExceptionStacktrace, ex.ToInvariantString() },
+ };
+
+ if (!string.IsNullOrWhiteSpace(ex.Message))
+ {
+ tagsCollection.Add(SemanticConventions.AttributeExceptionMessage, ex.Message);
+ }
+
+ activity?.AddEvent(new ActivityEvent(SemanticConventions.AttributeExceptionEventName, default, tagsCollection));
+ }
+
#pragma warning disable SA1201 // Elements should appear in the correct order
private static readonly Action SetKindProperty = CreateActivityKindSetter();
#pragma warning restore SA1201 // Elements should appear in the correct order
diff --git a/src/OpenTelemetry.Api/Trace/SemanticConventions.cs b/src/OpenTelemetry.Api/Trace/SemanticConventions.cs
index 75a11adae..b29cdd7dd 100644
--- a/src/OpenTelemetry.Api/Trace/SemanticConventions.cs
+++ b/src/OpenTelemetry.Api/Trace/SemanticConventions.cs
@@ -23,7 +23,8 @@ namespace OpenTelemetry.Trace
internal static class SemanticConventions
{
// The set of constants matches the specification as of this commit.
- // https://github.com/open-telemetry/opentelemetry-specification/tree/709293fe132709705f0e0dd4252992e87a6ec899/specification/trace/semantic_conventions
+ // https://github.com/open-telemetry/opentelemetry-specification/tree/master/specification/trace/semantic_conventions
+ // https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/exceptions.md
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
public const string AttributeServiceName = "service.name";
public const string AttributeServiceNamespace = "service.namespace";
@@ -147,6 +148,11 @@ namespace OpenTelemetry.Trace
public const string AttributeMessagingPayloadSize = "messaging.message_payload_size_bytes";
public const string AttributeMessagingPayloadCompressedSize = "messaging.message_payload_compressed_size_bytes";
public const string AttributeMessagingOperation = "messaging.operation";
+
+ public const string AttributeExceptionEventName = "exception";
+ public const string AttributeExceptionType = "exception.type";
+ public const string AttributeExceptionMessage = "exception.message";
+ public const string AttributeExceptionStacktrace = "exception.stacktrace";
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
}
}
diff --git a/src/OpenTelemetry.Api/Trace/TelemetrySpan.cs b/src/OpenTelemetry.Api/Trace/TelemetrySpan.cs
index 11e97a8e2..1d9e1f659 100644
--- a/src/OpenTelemetry.Api/Trace/TelemetrySpan.cs
+++ b/src/OpenTelemetry.Api/Trace/TelemetrySpan.cs
@@ -18,6 +18,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
+using OpenTelemetry.Internal;
namespace OpenTelemetry.Trace
{
@@ -303,6 +304,56 @@ namespace OpenTelemetry.Trace
return this;
}
+ ///
+ /// Record Exception.
+ ///
+ /// Exception to be recorded.
+ /// The instance for chaining.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public TelemetrySpan RecordException(Exception ex)
+ {
+ if (ex == null)
+ {
+ return this;
+ }
+
+ return this.RecordException(ex.GetType().Name, ex.Message, ex.ToInvariantString());
+ }
+
+ ///
+ /// Record Exception.
+ ///
+ /// Type of the exception to be recorded.
+ /// Message of the exception to be recorded.
+ /// Stacktrace of the exception to be recorded.
+ /// The instance for chaining.
+ public TelemetrySpan RecordException(string type, string message, string stacktrace)
+ {
+ Dictionary attributes = new Dictionary();
+
+ if (!string.IsNullOrWhiteSpace(type))
+ {
+ attributes.Add(SemanticConventions.AttributeExceptionType, type);
+ }
+
+ if (!string.IsNullOrWhiteSpace(stacktrace))
+ {
+ attributes.Add(SemanticConventions.AttributeExceptionStacktrace, stacktrace);
+ }
+
+ if (!string.IsNullOrWhiteSpace(message))
+ {
+ attributes.Add(SemanticConventions.AttributeExceptionMessage, message);
+ }
+
+ if (attributes.Count != 0)
+ {
+ this.AddEvent(SemanticConventions.AttributeExceptionEventName, attributes);
+ }
+
+ return this;
+ }
+
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
diff --git a/test/OpenTelemetry.Tests/Trace/ActivityExtensionsTest.cs b/test/OpenTelemetry.Tests/Trace/ActivityExtensionsTest.cs
index 1a6a71745..eff405f09 100644
--- a/test/OpenTelemetry.Tests/Trace/ActivityExtensionsTest.cs
+++ b/test/OpenTelemetry.Tests/Trace/ActivityExtensionsTest.cs
@@ -14,7 +14,9 @@
// limitations under the License.
//
+using System;
using System.Diagnostics;
+using System.Linq;
using Xunit;
namespace OpenTelemetry.Trace.Tests
@@ -114,5 +116,18 @@ namespace OpenTelemetry.Trace.Tests
Assert.Equal(inputOutput, activity.Kind);
}
+
+ [Fact]
+ public void CheckRecordException()
+ {
+ var message = "message";
+ var exception = new ArgumentNullException(message, new Exception(message));
+ var activity = new Activity("test-activity");
+ activity.RecordException(exception);
+
+ var @event = activity.Events.FirstOrDefault(e => e.Name == SemanticConventions.AttributeExceptionEventName);
+ Assert.Equal(message, @event.Tags.FirstOrDefault(t => t.Key == SemanticConventions.AttributeExceptionMessage).Value);
+ Assert.Equal(exception.GetType().Name, @event.Tags.FirstOrDefault(t => t.Key == SemanticConventions.AttributeExceptionType).Value);
+ }
}
}
diff --git a/test/OpenTelemetry.Tests/Trace/TelemetrySpanTest.cs b/test/OpenTelemetry.Tests/Trace/TelemetrySpanTest.cs
new file mode 100644
index 000000000..44d2f57e3
--- /dev/null
+++ b/test/OpenTelemetry.Tests/Trace/TelemetrySpanTest.cs
@@ -0,0 +1,72 @@
+//
+// 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.
+//
+
+using System;
+using System.Diagnostics;
+using System.Linq;
+using OpenTelemetry.Trace;
+using Xunit;
+
+namespace OpenTelemetry.Tests.Trace
+{
+ public class TelemetrySpanTest
+ {
+ [Fact]
+ public void CheckRecordExceptionData()
+ {
+ string message = "message";
+
+ using Activity activity = new Activity("exception-test");
+ using TelemetrySpan telemetrySpan = new TelemetrySpan(activity);
+ telemetrySpan.RecordException(new ArgumentNullException(message, new Exception("new-exception")));
+ Assert.Single(activity.Events);
+
+ var @event = telemetrySpan.Activity.Events.FirstOrDefault(q => q.Name == SemanticConventions.AttributeExceptionEventName);
+ Assert.Equal(message, @event.Tags.FirstOrDefault(t => t.Key == SemanticConventions.AttributeExceptionMessage).Value);
+ Assert.Equal(typeof(ArgumentNullException).Name, @event.Tags.FirstOrDefault(t => t.Key == SemanticConventions.AttributeExceptionType).Value);
+ }
+
+ [Fact]
+ public void CheckRecordExceptionData2()
+ {
+ string type = "ArgumentNullException";
+ string message = "message";
+ string stack = "stack";
+
+ using Activity activity = new Activity("exception-test");
+ using TelemetrySpan telemetrySpan = new TelemetrySpan(activity);
+ telemetrySpan.RecordException(type, message, stack);
+ Assert.Single(activity.Events);
+
+ var @event = telemetrySpan.Activity.Events.FirstOrDefault(q => q.Name == SemanticConventions.AttributeExceptionEventName);
+ Assert.Equal(message, @event.Tags.FirstOrDefault(t => t.Key == SemanticConventions.AttributeExceptionMessage).Value);
+ Assert.Equal(type, @event.Tags.FirstOrDefault(t => t.Key == SemanticConventions.AttributeExceptionType).Value);
+ Assert.Equal(stack, @event.Tags.FirstOrDefault(t => t.Key == SemanticConventions.AttributeExceptionStacktrace).Value);
+ }
+
+ [Fact]
+ public void CheckRecordExceptionEmpty()
+ {
+ using Activity activity = new Activity("exception-test");
+ using TelemetrySpan telemetrySpan = new TelemetrySpan(activity);
+ telemetrySpan.RecordException(string.Empty, string.Empty, string.Empty);
+ Assert.Empty(activity.Events);
+
+ telemetrySpan.RecordException(null);
+ Assert.Empty(activity.Events);
+ }
+ }
+}