diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln
index d3d368ae3c4..c4cd9ca811f 100644
--- a/OpenTelemetry.sln
+++ b/OpenTelemetry.sln
@@ -197,6 +197,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Shared", "src
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApp.AspNetCore.5.0", "test\TestApp.AspNetCore.5.0\TestApp.AspNetCore.5.0.csproj", "{972396A8-E35B-499C-9BA1-765E9B8822E1}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "exception-handling", "docs\trace\exception-handling\exception-handling.csproj", "{08D29501-F0A3-468F-B18D-BD1821A72383}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -387,6 +389,10 @@ Global
{972396A8-E35B-499C-9BA1-765E9B8822E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{972396A8-E35B-499C-9BA1-765E9B8822E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{972396A8-E35B-499C-9BA1-765E9B8822E1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {08D29501-F0A3-468F-B18D-BD1821A72383}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {08D29501-F0A3-468F-B18D-BD1821A72383}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {08D29501-F0A3-468F-B18D-BD1821A72383}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {08D29501-F0A3-468F-B18D-BD1821A72383}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -416,6 +422,7 @@ Global
{B3F03725-23A0-4582-9526-F6A7E38F35CC} = {3862190B-E2C5-418E-AFDC-DB281FB5C705}
{13C10C9A-07E8-43EB-91F5-C2B116FBE0FC} = {3862190B-E2C5-418E-AFDC-DB281FB5C705}
{972396A8-E35B-499C-9BA1-765E9B8822E1} = {77C7929A-2EED-4AA6-8705-B5C443C8AA0F}
+ {08D29501-F0A3-468F-B18D-BD1821A72383} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {55639B5C-0770-4A22-AB56-859604650521}
diff --git a/docs/Directory.Build.props b/docs/Directory.Build.props
index 3b7db6739a2..96d086bdd13 100644
--- a/docs/Directory.Build.props
+++ b/docs/Directory.Build.props
@@ -4,7 +4,7 @@
Exe
- netcoreapp2.1;netcoreapp3.1
+ netcoreapp2.1;netcoreapp3.1;net5.0
$(TargetFrameworks);net461;net462;net47;net471;net472;net48
diff --git a/docs/trace/exception-handling/Program.cs b/docs/trace/exception-handling/Program.cs
new file mode 100644
index 00000000000..1148dee2e1e
--- /dev/null
+++ b/docs/trace/exception-handling/Program.cs
@@ -0,0 +1,53 @@
+//
+// 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 OpenTelemetry;
+using OpenTelemetry.Trace;
+
+public class Program
+{
+ private static readonly ActivitySource MyActivitySource = new ActivitySource(
+ "MyCompany.MyProduct.MyLibrary");
+
+ public static void Main()
+ {
+ using var tracerProvider = Sdk.CreateTracerProviderBuilder(options =>
+ {
+ options.SetErrorStatusOnException = true;
+ })
+ .AddSource("MyCompany.MyProduct.MyLibrary")
+ .SetSampler(new AlwaysOnSampler())
+ .AddConsoleExporter()
+ .Build();
+
+ try
+ {
+ using (MyActivitySource.StartActivity("Foo"))
+ {
+ using (MyActivitySource.StartActivity("Bar"))
+ {
+ throw new Exception("Oops!");
+ }
+ }
+ }
+ catch (Exception)
+ {
+ // swallow the exception
+ }
+ }
+}
diff --git a/docs/trace/exception-handling/README.md b/docs/trace/exception-handling/README.md
new file mode 100644
index 00000000000..15fd107a5c3
--- /dev/null
+++ b/docs/trace/exception-handling/README.md
@@ -0,0 +1,115 @@
+# Exception Handling
+
+## User-handled Exception
+
+The term `User-handled Exception` is used to describe exceptions that are
+handled by the application.
+
+While using `Activity` API, the common pattern would be:
+
+```csharp
+using (var activity = MyActivitySource.StartActivity("Foo"))
+{
+ try
+ {
+ Func();
+ }
+ catch (SomeException ex)
+ {
+ activity?.SetStatus(Status.Error);
+ DoSomething();
+ }
+ catch (Exception)
+ {
+ activity?.SetStatus(Status.Error);
+ throw;
+ }
+}
+```
+
+The above approach could become hard to manage if there are deeply nested
+`Activity` objects, or there are activities created in a 3rd party library.
+
+The following configuration will automatically detect exception and set the
+activity status to `Error`:
+
+```csharp
+Sdk.CreateTracerProviderBuilder(options => {
+ options.SetErrorStatusOnException = true;
+});
+```
+
+A complete example can be found [here](./Program.cs).
+
+Note: this feature is platform dependent as it relies on
+[System.Runtime.InteropServices.Marshal.GetExceptionPointers](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.marshal.getexceptionpointers).
+
+## Unhandled Exception
+
+The term `Unhandled Exception` is used to describe exceptions that are not
+handled by the application. When an unhandled exception happened, the behavior
+will depend on the presence of a debugger:
+
+* If there is no debugger, the exception will normally crash the process or
+ terminate the thread.
+* If a debugger is attached, the debugger will be notified that an unhandled
+ exception happened.
+* In case a postmortem debugger is configured, the postmortem debugger will be
+ activited and normally it will collect a crash dump.
+
+It might be useful to automatically capture the unhandled exceptions, travel
+through the unfinished activities and export them for troubleshooting. Here goes
+one possible way of doing this:
+
+**WARNING:** Use `AppDomain.UnhandledException` with caution. A throw in the
+handler puts the process into an unrecoverable state.
+
+
+```csharp
+using System;
+using System.Diagnostics;
+using OpenTelemetry;
+using OpenTelemetry.Trace;
+
+public class Program
+{
+ private static readonly ActivitySource MyActivitySource = new ActivitySource("MyCompany.MyProduct.MyLibrary");
+
+ public static void Main()
+ {
+ AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
+
+ using var tracerProvider = Sdk.CreateTracerProviderBuilder(options =>
+ {
+ options.SetErrorStatusOnException = true;
+ })
+ .AddSource("MyCompany.MyProduct.MyLibrary")
+ .SetSampler(new AlwaysOnSampler())
+ .AddConsoleExporter()
+ .Build();
+
+ using (MyActivitySource.StartActivity("Foo"))
+ {
+ using (MyActivitySource.StartActivity("Bar"))
+ {
+ throw new Exception("Oops!");
+ }
+ }
+ }
+
+ private static void UnhandledExceptionHandler(object source, UnhandledExceptionEventArgs args)
+ {
+ var ex = (Exception)args.ExceptionObject;
+
+ var activity = Activity.Current;
+
+ while (activity != null)
+ {
+ activity.RecordException(ex);
+ activity.Dispose();
+ activity = activity.Parent;
+ }
+ }
+}
+```
+
diff --git a/docs/trace/exception-handling/exception-handling.csproj b/docs/trace/exception-handling/exception-handling.csproj
new file mode 100644
index 00000000000..afb37b73c59
--- /dev/null
+++ b/docs/trace/exception-handling/exception-handling.csproj
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/src/OpenTelemetry/.publicApi/net452/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net452/PublicAPI.Unshipped.txt
index f0dee03ad35..3b7ac888ecd 100644
--- a/src/OpenTelemetry/.publicApi/net452/PublicAPI.Unshipped.txt
+++ b/src/OpenTelemetry/.publicApi/net452/PublicAPI.Unshipped.txt
@@ -1,3 +1,8 @@
OpenTelemetry.Trace.ParentBasedSampler.ParentBasedSampler(OpenTelemetry.Trace.Sampler rootSampler, OpenTelemetry.Trace.Sampler remoteParentSampled = null, OpenTelemetry.Trace.Sampler remoteParentNotSampled = null, OpenTelemetry.Trace.Sampler localParentSampled = null, OpenTelemetry.Trace.Sampler localParentNotSampled = null) -> void
+OpenTelemetry.Trace.TracerProviderOptions
+OpenTelemetry.Trace.TracerProviderOptions.SetErrorStatusOnException.get -> bool
+OpenTelemetry.Trace.TracerProviderOptions.SetErrorStatusOnException.set -> void
+OpenTelemetry.Trace.TracerProviderOptions.TracerProviderOptions() -> void
+static OpenTelemetry.Sdk.CreateTracerProviderBuilder(System.Action configure = null) -> OpenTelemetry.Trace.TracerProviderBuilder
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddLegacyActivity(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, string operationName) -> OpenTelemetry.Trace.TracerProviderBuilder
static OpenTelemetry.Trace.TracerProviderExtensions.ForceFlush(this OpenTelemetry.Trace.TracerProvider provider, int timeoutMilliseconds = -1) -> bool
diff --git a/src/OpenTelemetry/.publicApi/net46/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net46/PublicAPI.Unshipped.txt
index f0dee03ad35..3b7ac888ecd 100644
--- a/src/OpenTelemetry/.publicApi/net46/PublicAPI.Unshipped.txt
+++ b/src/OpenTelemetry/.publicApi/net46/PublicAPI.Unshipped.txt
@@ -1,3 +1,8 @@
OpenTelemetry.Trace.ParentBasedSampler.ParentBasedSampler(OpenTelemetry.Trace.Sampler rootSampler, OpenTelemetry.Trace.Sampler remoteParentSampled = null, OpenTelemetry.Trace.Sampler remoteParentNotSampled = null, OpenTelemetry.Trace.Sampler localParentSampled = null, OpenTelemetry.Trace.Sampler localParentNotSampled = null) -> void
+OpenTelemetry.Trace.TracerProviderOptions
+OpenTelemetry.Trace.TracerProviderOptions.SetErrorStatusOnException.get -> bool
+OpenTelemetry.Trace.TracerProviderOptions.SetErrorStatusOnException.set -> void
+OpenTelemetry.Trace.TracerProviderOptions.TracerProviderOptions() -> void
+static OpenTelemetry.Sdk.CreateTracerProviderBuilder(System.Action configure = null) -> OpenTelemetry.Trace.TracerProviderBuilder
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddLegacyActivity(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, string operationName) -> OpenTelemetry.Trace.TracerProviderBuilder
static OpenTelemetry.Trace.TracerProviderExtensions.ForceFlush(this OpenTelemetry.Trace.TracerProvider provider, int timeoutMilliseconds = -1) -> bool
diff --git a/src/OpenTelemetry/.publicApi/net461/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net461/PublicAPI.Unshipped.txt
index f0dee03ad35..3b7ac888ecd 100644
--- a/src/OpenTelemetry/.publicApi/net461/PublicAPI.Unshipped.txt
+++ b/src/OpenTelemetry/.publicApi/net461/PublicAPI.Unshipped.txt
@@ -1,3 +1,8 @@
OpenTelemetry.Trace.ParentBasedSampler.ParentBasedSampler(OpenTelemetry.Trace.Sampler rootSampler, OpenTelemetry.Trace.Sampler remoteParentSampled = null, OpenTelemetry.Trace.Sampler remoteParentNotSampled = null, OpenTelemetry.Trace.Sampler localParentSampled = null, OpenTelemetry.Trace.Sampler localParentNotSampled = null) -> void
+OpenTelemetry.Trace.TracerProviderOptions
+OpenTelemetry.Trace.TracerProviderOptions.SetErrorStatusOnException.get -> bool
+OpenTelemetry.Trace.TracerProviderOptions.SetErrorStatusOnException.set -> void
+OpenTelemetry.Trace.TracerProviderOptions.TracerProviderOptions() -> void
+static OpenTelemetry.Sdk.CreateTracerProviderBuilder(System.Action configure = null) -> OpenTelemetry.Trace.TracerProviderBuilder
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddLegacyActivity(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, string operationName) -> OpenTelemetry.Trace.TracerProviderBuilder
static OpenTelemetry.Trace.TracerProviderExtensions.ForceFlush(this OpenTelemetry.Trace.TracerProvider provider, int timeoutMilliseconds = -1) -> bool
diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt
index f0dee03ad35..3b7ac888ecd 100644
--- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt
+++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt
@@ -1,3 +1,8 @@
OpenTelemetry.Trace.ParentBasedSampler.ParentBasedSampler(OpenTelemetry.Trace.Sampler rootSampler, OpenTelemetry.Trace.Sampler remoteParentSampled = null, OpenTelemetry.Trace.Sampler remoteParentNotSampled = null, OpenTelemetry.Trace.Sampler localParentSampled = null, OpenTelemetry.Trace.Sampler localParentNotSampled = null) -> void
+OpenTelemetry.Trace.TracerProviderOptions
+OpenTelemetry.Trace.TracerProviderOptions.SetErrorStatusOnException.get -> bool
+OpenTelemetry.Trace.TracerProviderOptions.SetErrorStatusOnException.set -> void
+OpenTelemetry.Trace.TracerProviderOptions.TracerProviderOptions() -> void
+static OpenTelemetry.Sdk.CreateTracerProviderBuilder(System.Action configure = null) -> OpenTelemetry.Trace.TracerProviderBuilder
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddLegacyActivity(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, string operationName) -> OpenTelemetry.Trace.TracerProviderBuilder
static OpenTelemetry.Trace.TracerProviderExtensions.ForceFlush(this OpenTelemetry.Trace.TracerProvider provider, int timeoutMilliseconds = -1) -> bool
diff --git a/src/OpenTelemetry/CHANGELOG.md b/src/OpenTelemetry/CHANGELOG.md
index 74af3d7381b..4fe5e34da32 100644
--- a/src/OpenTelemetry/CHANGELOG.md
+++ b/src/OpenTelemetry/CHANGELOG.md
@@ -9,14 +9,15 @@ please check the latest changes
## Unreleased
-* Added `ForceFlush` to `TracerProvider`. ([#1837](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1837))
-
+* Added `TracerProviderOptions` and `SetErrorStatusOnException`.
+ ([#1858](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1858))
+* Added `ForceFlush` to `TracerProvider`.
+ ([#1837](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1837))
* Added a TracerProvierBuilder extension method called
`AddLegacyActivityOperationName` which is used by instrumentation libraries
that use DiagnosticSource to get activities processed without
ActivitySourceAdapter.
- [#1836](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1836)
-
+ ([#1836](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1836))
* Added new constructor with optional parameters to allow customization of
`ParentBasedSampler` behavior. ([#1727](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1727))
diff --git a/src/OpenTelemetry/Sdk.cs b/src/OpenTelemetry/Sdk.cs
index 276650df257..ea1899e21fc 100644
--- a/src/OpenTelemetry/Sdk.cs
+++ b/src/OpenTelemetry/Sdk.cs
@@ -14,6 +14,7 @@
// limitations under the License.
//
+using System;
using System.Diagnostics;
using OpenTelemetry.Context.Propagation;
using OpenTelemetry.Internal;
@@ -54,13 +55,24 @@ public static void SetDefaultTextMapPropagator(TextMapPropagator textMapPropagat
}
///
- /// Creates TracerProviderBuilder which should be used to build
- /// TracerProvider.
+ /// Creates TracerProviderBuilder which should be used to build TracerProvider.
///
/// TracerProviderBuilder instance, which should be used to build TracerProvider.
public static TracerProviderBuilder CreateTracerProviderBuilder()
{
- return new TracerProviderBuilderSdk();
+ return CreateTracerProviderBuilder(null);
+ }
+
+ ///
+ /// Creates TracerProviderBuilder which should be used to build TracerProvider.
+ ///
+ /// TracerProvider configuration options.
+ /// TracerProviderBuilder instance, which should be used to build TracerProvider.
+ public static TracerProviderBuilder CreateTracerProviderBuilder(Action configure = null)
+ {
+ var options = new TracerProviderOptions();
+ configure?.Invoke(options);
+ return new TracerProviderBuilderSdk(options);
}
}
}
diff --git a/src/OpenTelemetry/Trace/ExceptionProcessor.cs b/src/OpenTelemetry/Trace/ExceptionProcessor.cs
new file mode 100644
index 00000000000..16ebcfa14b9
--- /dev/null
+++ b/src/OpenTelemetry/Trace/ExceptionProcessor.cs
@@ -0,0 +1,80 @@
+//
+// 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.Expressions;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+namespace OpenTelemetry.Trace
+{
+ internal class ExceptionProcessor : BaseProcessor
+ {
+ private const string ExceptionPointersKey = "otel.exception_pointers";
+
+ private readonly Func fnGetExceptionPointers;
+
+ public ExceptionProcessor()
+ {
+ try
+ {
+ var flags = BindingFlags.Static | BindingFlags.Public;
+ var method = typeof(Marshal).GetMethod("GetExceptionPointers", flags, null, new Type[] { }, null);
+ var lambda = Expression.Lambda>(Expression.Call(method));
+ this.fnGetExceptionPointers = lambda.Compile();
+ }
+ catch (Exception ex)
+ {
+ throw new NotSupportedException("System.Runtime.InteropServices.Marshal.GetExceptionPointers is not supported.", ex);
+ }
+ }
+
+ ///
+ public override void OnStart(Activity activity)
+ {
+ var pointers = this.fnGetExceptionPointers();
+
+ if (pointers != IntPtr.Zero)
+ {
+ activity.SetTag(ExceptionPointersKey, pointers);
+ }
+ }
+
+ ///
+ public override void OnEnd(Activity activity)
+ {
+ var pointers = this.fnGetExceptionPointers();
+
+ if (pointers == IntPtr.Zero)
+ {
+ return;
+ }
+
+ var snapshot = activity.GetTagValue(ExceptionPointersKey) as IntPtr?;
+
+ if (snapshot != null)
+ {
+ activity.SetTag(ExceptionPointersKey, null);
+ }
+
+ if (snapshot != pointers)
+ {
+ activity.SetStatus(Status.Error);
+ }
+ }
+ }
+}
diff --git a/src/OpenTelemetry/Trace/TracerProviderBuilderSdk.cs b/src/OpenTelemetry/Trace/TracerProviderBuilderSdk.cs
index cfa21e5d896..6f058534241 100644
--- a/src/OpenTelemetry/Trace/TracerProviderBuilderSdk.cs
+++ b/src/OpenTelemetry/Trace/TracerProviderBuilderSdk.cs
@@ -28,14 +28,28 @@ internal class TracerProviderBuilderSdk : TracerProviderBuilder
{
private readonly List instrumentationFactories = new List();
+ private readonly TracerProviderOptions options;
private readonly List> processors = new List>();
private readonly List sources = new List();
private readonly Dictionary legacyActivityOperationNames = new Dictionary(StringComparer.OrdinalIgnoreCase);
private ResourceBuilder resourceBuilder = ResourceBuilder.CreateDefault();
private Sampler sampler = new ParentBasedSampler(new AlwaysOnSampler());
- internal TracerProviderBuilderSdk()
+ internal TracerProviderBuilderSdk(TracerProviderOptions options)
{
+ this.options = options ?? new TracerProviderOptions();
+
+ if (options.SetErrorStatusOnException)
+ {
+ try
+ {
+ this.AddProcessor(new ExceptionProcessor());
+ }
+ catch (Exception ex)
+ {
+ throw new NotSupportedException($"{nameof(options.SetErrorStatusOnException)} is not supported on this platform.", ex);
+ }
+ }
}
///
diff --git a/src/OpenTelemetry/Trace/TracerProviderOptions.cs b/src/OpenTelemetry/Trace/TracerProviderOptions.cs
new file mode 100644
index 00000000000..7ec4b852b6c
--- /dev/null
+++ b/src/OpenTelemetry/Trace/TracerProviderOptions.cs
@@ -0,0 +1,27 @@
+//
+// 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.
+//
+
+namespace OpenTelemetry.Trace
+{
+ public class TracerProviderOptions
+ {
+ ///
+ /// Gets or sets a value indicating whether the status of
+ /// should be set to Status.Error when it ended abnormally due to an unhandled exception.
+ ///
+ public bool SetErrorStatusOnException { get; set; } = false;
+ }
+}
diff --git a/test/OpenTelemetry.Tests/Trace/ExceptionProcessorTest.cs b/test/OpenTelemetry.Tests/Trace/ExceptionProcessorTest.cs
new file mode 100644
index 00000000000..97e4eeaec36
--- /dev/null
+++ b/test/OpenTelemetry.Tests/Trace/ExceptionProcessorTest.cs
@@ -0,0 +1,120 @@
+//
+// 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 OpenTelemetry.Tests;
+using Xunit;
+
+namespace OpenTelemetry.Trace.Tests
+{
+ public class ExceptionProcessorTest
+ {
+ private const string ActivitySourceName = "ExceptionProcessorTest";
+
+ [Fact]
+ public void ActivityStatusSetToErrorWhenExceptionProcessorEnabled()
+ {
+ using var activitySource = new ActivitySource(ActivitySourceName);
+ using var tracerProvider = Sdk.CreateTracerProviderBuilder()
+ .AddSource(ActivitySourceName)
+ .SetSampler(new AlwaysOnSampler())
+ .AddProcessor(new ExceptionProcessor())
+ .Build();
+
+ Activity activity1 = null;
+ Activity activity2 = null;
+ Activity activity3 = null;
+ Activity activity4 = null;
+ Activity activity5 = null;
+
+ try
+ {
+ using (activity1 = activitySource.StartActivity("Activity1"))
+ {
+ using (activity2 = activitySource.StartActivity("Activity2"))
+ {
+ throw new Exception("Oops!");
+ }
+ }
+ }
+ catch (Exception)
+ {
+ using (activity3 = activitySource.StartActivity("Activity3"))
+ {
+ }
+ }
+ finally
+ {
+ using (activity4 = activitySource.StartActivity("Activity4"))
+ {
+ }
+ }
+
+ try
+ {
+ throw new Exception("Oops!");
+ }
+ catch (Exception)
+ {
+ activity5 = activitySource.StartActivity("Activity5");
+ }
+ finally
+ {
+ activity5.Dispose();
+ }
+
+ Assert.Equal(StatusCode.Error, activity1.GetStatus().StatusCode);
+ Assert.Null(activity1.GetTagValue("otel.exception_pointers"));
+ Assert.Equal(StatusCode.Error, activity2.GetStatus().StatusCode);
+ Assert.Null(activity2.GetTagValue("otel.exception_pointers"));
+ Assert.Equal(StatusCode.Unset, activity3.GetStatus().StatusCode);
+ Assert.Null(activity3.GetTagValue("otel.exception_pointers"));
+ Assert.Equal(StatusCode.Unset, activity4.GetStatus().StatusCode);
+ Assert.Null(activity4.GetTagValue("otel.exception_pointers"));
+ Assert.Equal(StatusCode.Unset, activity5.GetStatus().StatusCode);
+#if !NETFRAMEWORK
+ // In this rare case, the following Activity tag will not get cleaned up due to perf consideration.
+ Assert.NotNull(activity5.GetTagValue("otel.exception_pointers"));
+#endif
+ }
+
+ [Fact]
+ public void ActivityStatusNotSetWhenExceptionProcessorNotEnabled()
+ {
+ using var activitySource = new ActivitySource(ActivitySourceName);
+ using var tracerProvider = Sdk.CreateTracerProviderBuilder()
+ .AddSource(ActivitySourceName)
+ .SetSampler(new AlwaysOnSampler())
+ .Build();
+
+ Activity activity = null;
+
+ try
+ {
+ using (activity = activitySource.StartActivity("Activity"))
+ {
+ throw new Exception("Oops!");
+ }
+ }
+ catch (Exception)
+ {
+ }
+
+ Assert.Equal(StatusCode.Unset, activity.GetStatus().StatusCode);
+ }
+ }
+}
diff --git a/test/OpenTelemetry.Tests/Trace/TracerProviderOptionsTest.cs b/test/OpenTelemetry.Tests/Trace/TracerProviderOptionsTest.cs
new file mode 100644
index 00000000000..7e4b456d9fb
--- /dev/null
+++ b/test/OpenTelemetry.Tests/Trace/TracerProviderOptionsTest.cs
@@ -0,0 +1,81 @@
+//
+// 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 OpenTelemetry.Tests;
+using Xunit;
+
+namespace OpenTelemetry.Trace.Tests
+{
+ public class TracerProviderOptionsTest
+ {
+ private const string ActivitySourceName = "TracerProviderOptionsTest";
+
+ [Fact]
+ public void SetErrorStatusOnExceptionEnabled()
+ {
+ using var activitySource = new ActivitySource(ActivitySourceName);
+ using var tracerProvider = Sdk.CreateTracerProviderBuilder(options =>
+ {
+ options.SetErrorStatusOnException = true;
+ })
+ .AddSource(ActivitySourceName)
+ .SetSampler(new AlwaysOnSampler())
+ .Build();
+
+ var activity = activitySource.StartActivity("Activity");
+
+ try
+ {
+ using (activity)
+ {
+ throw new Exception("Oops!");
+ }
+ }
+ catch (Exception)
+ {
+ }
+
+ Assert.Equal(StatusCode.Error, activity.GetStatus().StatusCode);
+ }
+
+ [Fact]
+ public void SetErrorStatusOnExceptionDefault()
+ {
+ using var activitySource = new ActivitySource(ActivitySourceName);
+ using var tracerProvider = Sdk.CreateTracerProviderBuilder(options => { })
+ .AddSource(ActivitySourceName)
+ .SetSampler(new AlwaysOnSampler())
+ .Build();
+
+ var activity = activitySource.StartActivity("Activity");
+
+ try
+ {
+ using (activity)
+ {
+ throw new Exception("Oops!");
+ }
+ }
+ catch (Exception)
+ {
+ }
+
+ Assert.Equal(StatusCode.Unset, activity.GetStatus().StatusCode);
+ }
+ }
+}