-
Notifications
You must be signed in to change notification settings - Fork 772
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
Add SetErrorStatusOnException option to TracerProviderSdk #1858
Changes from 4 commits
8494e39
106fb83
33d6817
bee267a
886fdcc
bb99d9a
db4141d
266ccf9
42228b6
8a8b0c8
dbd4f44
a2d332d
1252df2
e2a9cdd
b7daf81
a6e2356
b54d0ba
1332c8d
9bf74a3
4cdf023
76b8e25
ecffe5d
b41ca42
c7b115f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
// <copyright file="Program.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; | ||
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.SetErrorStatusOnUnhandledException = true; | ||
}) | ||
.SetSampler(new AlwaysOnSampler()) | ||
.AddSource("MyCompany.MyProduct.MyLibrary") | ||
.AddConsoleExporter() | ||
.Build(); | ||
|
||
try | ||
{ | ||
Func(); | ||
} | ||
catch (Exception) | ||
{ | ||
// swallow the exception | ||
} | ||
} | ||
|
||
public static void Func() | ||
{ | ||
using (MyActivitySource.StartActivity("Foo")) | ||
{ | ||
using (MyActivitySource.StartActivity("Bar")) | ||
{ | ||
throw new Exception("Oops!"); | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
# Exception Handling | ||
|
||
## First Chance Exception | ||
|
||
The term `First Chance Exception` is used to describe exceptions that are | ||
handled by the application - whether in the user code or the framework. | ||
reyang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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 first chance exception and | ||
automatically set the activity status to `Error`: | ||
|
||
```csharp | ||
Sdk.CreateTracerProviderBuilder(options => { | ||
options.SetErrorStatusOnUnhandledException = true; | ||
}); | ||
``` | ||
|
||
A complete example can be found [here](./Program.cs). | ||
|
||
Note: this feature is platform dependant as it relies on | ||
[System.Runtime.InteropServices.Marshal.GetExceptionPointers](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.marshal.getexceptionpointers). | ||
|
||
## Second Chance Exception | ||
|
||
The term `Second Chance Exception` is used to describe exceptions that are not | ||
handled by the application. | ||
|
||
When a second chance 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. | ||
|
||
Handling _unhandled exception_ is a very dangerous thing since the handler | ||
reyang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
itself could introduce exception, which would result in an unrecoverable | ||
situation similar to [triple fault](https://en.wikipedia.org/wiki/Triple_fault). | ||
|
||
In a non-production environment, 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: | ||
|
||
```csharp | ||
static void Main() | ||
{ | ||
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler; | ||
} | ||
|
||
static void UnhandledExceptionHandler(object source, UnhandledExceptionEventArgs args) | ||
{ | ||
var activity = Activity.Current; | ||
|
||
while (activity != null) | ||
{ | ||
activity.Dispose(); | ||
activity = activity.Parent; | ||
} | ||
} | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<ItemGroup> | ||
<!--- | ||
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="$(OpenTelemetryExporterConsolePkgVer)" /> | ||
--> | ||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Console\OpenTelemetry.Exporter.Console.csproj" /> | ||
</ItemGroup> | ||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// <copyright file="ExceptionProcessor.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; | ||
using System.Diagnostics; | ||
using System.Linq.Expressions; | ||
using System.Reflection; | ||
using System.Runtime.InteropServices; | ||
|
||
namespace OpenTelemetry.Trace | ||
{ | ||
internal class ExceptionProcessor : BaseProcessor<Activity> | ||
{ | ||
private readonly Func<IntPtr> 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<Func<IntPtr>>(Expression.Call(method)); | ||
this.fnGetExceptionPointers = lambda.Compile(); | ||
} | ||
catch (Exception ex) | ||
{ | ||
throw new NotSupportedException("System.Runtime.InteropServices.Marshal.GetExceptionPointers is not supported.", ex); | ||
reyang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override void OnEnd(Activity activity) | ||
reyang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
if (this.fnGetExceptionPointers() != IntPtr.Zero) | ||
{ | ||
activity.SetStatus(Status.Error); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible to get the type, message, and/or stack from the IntPtr returned from GetExceptionPointers? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes it is possible by traveling through the SEH object. This is CPU/OS/platform dependent and not well documented. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks petty intense! Not sure if it is worth attempting. Why I bring it up: I was just thinking about the applications I have instrumented with OpenTelemetry .NET so far. There are really only a few spots I've had the need to create spans manually. Almost exclusively around queues/topics. In those cases, I'm doing what the example has, with one exception... catch (Exception ex)
{
activity.SetStatus(Status.Error.WithDescription(ex.Message));
throw;
} ...instead of... catch (Exception)
{
activity.SetStatus(Status.Error);
throw;
} So, commenting from the perspective of a user of the library, since there's only a handful of spots where I have the try/catch, and the message/description is super useful, I'll probably continue doing the try/catch instead of using the processor/option. But if the processor could grab the message too, that would be something. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm happy to cover it in a follow up PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm using #1874 to explore how deep should we go. |
||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// <copyright file="TracerProviderOptions.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> | ||
|
||
namespace OpenTelemetry.Trace | ||
{ | ||
public class TracerProviderOptions | ||
{ | ||
/// <summary> | ||
/// Gets or sets a value indicating whether the status of <see cref="System.Diagnostics.Activity"/> | ||
/// should be set to <c>Status.Error</c> when it ended abnormally due to an unhandled exception. | ||
/// </summary> | ||
public bool SetErrorStatusOnUnhandledException { get; set; } = false; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❤️ this guide 😄