Skip to content

[Instrumentation.Wcf] Allow WCF services to record exceptions if RecordException is set#2880

Merged
Kielek merged 9 commits into
open-telemetry:mainfrom
AngleOSaxon:wcf-error-errorhandler
Aug 18, 2025
Merged

[Instrumentation.Wcf] Allow WCF services to record exceptions if RecordException is set#2880
Kielek merged 9 commits into
open-telemetry:mainfrom
AngleOSaxon:wcf-error-errorhandler

Conversation

@AngleOSaxon
Copy link
Copy Markdown
Contributor

Implements #2878

Changes

Adds an IErrorHandler implementation (TracingErrorHandler) that gets added to the ChannelDispatcher instances for any instrumented services. If the RecordExceptions option is set, it associates the current Activity with OperationContext.Current during the initial message inspection. When an exception is triggered, the TracingErrorHandler loads the Activity and adds the exception to it.

Although the documentation specifically notes that the HandleError method should be used to perform activities such as logging, I'm performing the logging in the ProvideFault method. This is being done for reasons of timing--ProvideFault is called before the message is returned to the client and therefore before the Activity is stopped in TelemetryDispatchMessageInspector.BeforeSendReply. This allows the exception to be properly placed inside the recorded span, prevents it from being attached to an already completed Activity, and ensures the OperationContext with the relevant information still exists.

It also simplifies the testing considerably, because I have not been able to find an event that only fires after all HandleError implementations have been called, so I'm forced to simply rely on predefined waits to see if the error is recorded.

As far as I am aware, the biggest potential issue with using ProvideFault is it will block completing the request and sending the response to the client. I don't expect that to be an issue here, but it will rely on the good behavior of the ActivityListener.ExceptionRecorder implementations.

Merge requirement checklist

  • CONTRIBUTING guidelines followed (license requirements, nullable enabled, static analysis, etc.)
  • Unit tests added/updated
  • Appropriate CHANGELOG.md files updated for non-trivial changes
  • Changes in public API reviewed (if applicable)

@AngleOSaxon AngleOSaxon requested a review from a team as a code owner July 5, 2025 19:55
@linux-foundation-easycla
Copy link
Copy Markdown

linux-foundation-easycla Bot commented Jul 5, 2025

CLA Signed

The committers listed above are authorized under a signed CLA.

@github-actions github-actions Bot added the comp:instrumentation.wcf Things related to OpenTelemetry.Instrumentation.Wcf label Jul 5, 2025
@github-actions
Copy link
Copy Markdown
Contributor

This PR was marked stale due to lack of activity. It will be closed in 7 days.

@github-actions github-actions Bot added the Stale label Jul 13, 2025

// OperationContext.Current *should* be reliable even in async calls at .NET 4.6.2+.
// In older versions it may not be.
var context = OperationContext.Current?.Extensions.FirstOrDefault(item => item is WcfOperationContext) as WcfOperationContext;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

IExtensionCollection<T>.Find<E> would be more concise.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed

@github-actions github-actions Bot removed the Stale label Jul 14, 2025
Copy link
Copy Markdown
Member

@Kielek Kielek left a comment

Choose a reason for hiding this comment

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

@mconnew, is there any chance that you find some time to review general concept in this PR?

Comment thread src/OpenTelemetry.Instrumentation.Wcf/CHANGELOG.md
Comment on lines +234 to +237
activityListener.ExceptionRecorder += (Activity activity, Exception ex, ref TagList tags) =>
{
recordedExceptions.Add(ex);
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I considered whether this ExceptionRecorder should check that activity.Source.Name is as expected. Just to verify that the exception is being recorded by the OpenTelemetry WCF instrumentation rather than any WCF built-in feature. However, if WCF somehow got such a feature, then the recordException: false test cases would fail. So it's not necessary to check the ActivitySource here.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WCF in .NET Framework isn't adding anything like this. We were going to initially, but the goal posts kept moving on what telemetry was going to look like, and they keep changing in small subtle ways (evolving), so adding telemetry externally via this package is going to be the best place for it to exist for a long time. And the more time passes, the less interest there is in adding anything to .NET Framework as all the focus is on .NET.


if (recordException && triggerException)
{
Assert.Collection(recordedExceptions, e => Assert.IsType<Exception>(e));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

OK, this verifies the type of the exception is exactly Exception as thrown from test/OpenTelemetry.Instrumentation.Wcf.Tests/WCF/Service.netfx.cs, not translated to any FaultException.

Comment on lines +107 to +110
if (WcfInstrumentationActivitySource.Options.RecordException)
{
OperationContext.Current?.Extensions.Add(new WcfOperationContext(activity));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I have been doing something similar, and yes, OperationContext.Current will be more reliable for this use than Activity.Current, because when the WCF dispatcher pauses an operation and resumes another operation, it restores only OperationContext and not the whole ExecutionContext.

My implementation additionally has an ICallContextInitializer that finds the (already started) Activity from OperationContext.Current and sets it as Activity.Current. I added that because I suspected that throttling in the WCF dispatcher might mess up the AsyncLocal<T> states. However, I haven't yet tested whether that actually happens.

This OpenTelemetry WCF instrumentation doesn't seem to have such an ICallContextInitializer. Perhaps one should be added; but this PR does not make the problem worse.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I believe you are correct that the throttles would mess up the AsyncLocal<T> state as it essentially maintains a list of continuations to run in order as throttles are released and the ExecutionContext is captured with context flow disabled. Definitely worth adding this as a follow up item.

@AngleOSaxon AngleOSaxon force-pushed the wcf-error-errorhandler branch from d4421c8 to 7c66aaa Compare July 14, 2025 21:17
AngleOSaxon and others added 4 commits July 20, 2025 13:15
@AngleOSaxon AngleOSaxon force-pushed the wcf-error-errorhandler branch from 7c66aaa to 46659e6 Compare July 20, 2025 17:15
AngleOSaxon and others added 2 commits July 23, 2025 20:32
Co-authored-by: Matt Hensley <130569+matt-hensley@users.noreply.github.com>
@KalleOlaviNiemitalo
Copy link
Copy Markdown

KalleOlaviNiemitalo commented Jul 24, 2025

Should mention the feature around here, rather than say it "is available only on the client side":

### RecordException
This instrumentation automatically sets Activity Status to Error if an unhandled
exception is thrown. Additionally, `RecordException` feature may be turned on,
to store the exception to the Activity itself as ActivityEvent. `RecordException`
is available only on the client side.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Aug 1, 2025

This PR was marked stale due to lack of activity. It will be closed in 7 days.

@github-actions github-actions Bot added the Stale label Aug 1, 2025
@AngleOSaxon
Copy link
Copy Markdown
Contributor Author

Should mention the feature around here, rather than say it "is available only on the client side":

### RecordException
This instrumentation automatically sets Activity Status to Error if an unhandled
exception is thrown. Additionally, `RecordException` feature may be turned on,
to store the exception to the Activity itself as ActivityEvent. `RecordException`
is available only on the client side.

Done

@mconnew
Copy link
Copy Markdown

mconnew commented Aug 3, 2025

@kiel, sorry I missed the mention earlier. I'll take a look this week.

@github-actions github-actions Bot removed the Stale label Aug 3, 2025
@github-actions
Copy link
Copy Markdown
Contributor

This PR was marked stale due to lack of activity. It will be closed in 7 days.

@github-actions github-actions Bot added the Stale label Aug 11, 2025
// so the error appears after the Activity has completed. Additionally, sometimes
// the context has already been disposed or otherwise lost, preventing association
// at all.
// Also it becomes very difficult to unit-test, because there is no easy `ErrorsHandled`
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is perfectly fine to do here. HandleError allows you to prevent the session/context/connection from being aborted, and ProvideFault enable you to provde a custom fault message. Neither one of them are designed specifically for what you're doing, so there's no "right" place to do this. HanndleError is no more the right place than ProvideFault, so if this method works better due to the activity being stopped, then it's the right method to use.

var context = OperationContext.Current?.Extensions.Find<WcfOperationContext>();
var activity = context?.Activity ?? WcfInstrumentationActivitySource.ActivitySource.StartActivity(WcfInstrumentationActivitySource.UnassociatedExceptionActivityName, ActivityKind.Internal);

activity?.AddException(error);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Do you need the null coalescing here? I presume StartActivity is guaranteed to return a non-null activity, which means activity can't be null here. Same with the call to Stop below.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I presume StartActivity is guaranteed to return a non-null activity

It's not. StartActivity returns null if the ActivitySource has no listeners or if no ActivityListener chooses to sample the activity.

@mconnew
Copy link
Copy Markdown

mconnew commented Aug 11, 2025

I've approved the changes, but I don't have write access to this repo, so it's still blocked.

@github-actions github-actions Bot removed the Stale label Aug 12, 2025
@Kielek Kielek changed the title Allow WCF services to record exceptions if RecordException is set [Instrumentation.Wcf] Allow WCF services to record exceptions if RecordException is set Aug 18, 2025
Copy link
Copy Markdown
Member

@Kielek Kielek left a comment

Choose a reason for hiding this comment

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

Code changes LGTM..
@mconnew is domain expert here, so I am approving based on his judgement.

@codecov
Copy link
Copy Markdown

codecov Bot commented Aug 18, 2025

Codecov Report

❌ Patch coverage is 80.95238% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.76%. Comparing base (b1767d6) to head (35ef4e9).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...entation.Wcf/Implementation/TracingErrorHandler.cs 88.88% 1 Missing ⚠️
...entation.Wcf/Implementation/WcfOperationContext.cs 83.33% 1 Missing ⚠️
...entation.Wcf/TelemetryContractBehaviorAttribute.cs 0.00% 1 Missing ⚠️
...ry.Instrumentation.Wcf/TelemetryServiceBehavior.cs 0.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #2880      +/-   ##
==========================================
+ Coverage   69.59%   69.76%   +0.16%     
==========================================
  Files         396      388       -8     
  Lines       15906    15876      -30     
==========================================
+ Hits        11070    11076       +6     
+ Misses       4836     4800      -36     
Flag Coverage Δ
unittests-Instrumentation.Cassandra ?
unittests-Instrumentation.Wcf 78.95% <80.95%> (+0.20%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...mplementation/TelemetryDispatchMessageInspector.cs 90.76% <100.00%> (+1.88%) ⬆️
...Implementation/WcfInstrumentationActivitySource.cs 100.00% <100.00%> (ø)
...y.Instrumentation.Wcf/TelemetryEndpointBehavior.cs 91.42% <100.00%> (+0.25%) ⬆️
...entation.Wcf/Implementation/TracingErrorHandler.cs 88.88% <88.88%> (ø)
...entation.Wcf/Implementation/WcfOperationContext.cs 83.33% <83.33%> (ø)
...entation.Wcf/TelemetryContractBehaviorAttribute.cs 0.00% <0.00%> (ø)
...ry.Instrumentation.Wcf/TelemetryServiceBehavior.cs 0.00% <0.00%> (ø)

... and 10 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Kielek Kielek merged commit 6ddcd9a into open-telemetry:main Aug 18, 2025
63 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp:instrumentation.wcf Things related to OpenTelemetry.Instrumentation.Wcf

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants