-
Notifications
You must be signed in to change notification settings - Fork 67
[WIP] Library instrumentation guidance review #917
Conversation
LibraryInstrumentationGuidance.md
Outdated
|
||
```csharp | ||
// create DiagnosticListener with unique name | ||
static DiagnosticListener source = new DiagnosticListener("Example.MyLibrary"); |
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.
I would change Example.MyLibrary, to something like 'NameSpace.ComponentName' to give people guidance on what a good name looks like
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 static variable should be explained in a separate code block IMO to better differentiate it from the actual instrumentation code.
LibraryInstrumentationGuidance.md
Outdated
activity = new Activity("Example.MyLibrary.MyOperation"); | ||
source.StartActivity(activity, new { Input = input }); | ||
activity = new Activity("MyOperation"); | ||
activity.Start(); |
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.
You can simplify to
activity = new Activity("MyOperation").Start();
But we probably want to make a helper 'DiagnosticListener.StartIfEnabled(string)' which does the lines above (and inlines), and thus the guidance is to do
Activity activity = source.StartIfEnabled("MyOperation")
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.
See other comment. Firing the "Start"-event should be a MUST. It's a complete mess if this is optional and only done by a few libraries.
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.
Having "activity.Start()" and "source.StartActivity()" is very confusing anyway and I too think that this should be simplified somehow in the API.
LibraryInstrumentationGuidance.md
Outdated
} | ||
catch (Exception ex) | ||
{ | ||
activity?.AddTag("error", true); |
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.
Tags values are strings so this has to be
activity?.AddTag("error", "true");
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.
We should probably add special helpers like 'AddErrorTag' for canonical tags as we find they are very generic. That makes the code simpler and allows Activity to encode it more efficiently in the future.
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.
having 'status' on the Activity would be great once we agree on the contract!
TL;DR;
So, I would recommend setting a status code and optionally sending exception event.
Status should still provide "OK" or "something else" (like OpenCensus does). Having strict enum-kind of mapping could be an option too, but it seems hard to do this mapping for each exception in the library
Since the result of operation is special and essential, I think we should have a Status
property (and SetStatus(string)
method) on the Activity.
I put "error" as a placeholder and do not think we should use it. Boolean value as OpenTracing suggests here does not seem to be informative enough: HTTP, gRPC and perhaps other communication would rather provide a status code. OpenCensus suggests status code + details.
While it is useful to stick to the tags suggested by OpenTracing, I don't really understand the guidance there as well.
@cwe1ss, could you please advise us, what is the OpenTracing recommendation for errors
I see suggestion to use
event="error"
error.object=<error object instance>
where possible. What about generic (non http) status recommendation in the tags?
This recommendation translates to DiagnosticSource 'Exception' event rather than a tag on the Span/Activity.
After working with exception events for a while, I can see an issue with them:
We expected someone would be interested in exceptions only without Activities. It seems tracking ALL exceptions without context is not that useful - it could mean overwhelming amount of handled exceptions and we hear users are interested mostly in unhandled ones.
Because of the same assumptions they complicate instrumentation a bit.
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.
Status code
OpenTracing currently only defines the "http.status_code" header and no non-http status codes. As there's no real protocol-independent standard (or none that I know of), I doubt that it makes sense to have a generic "status" tag/property. Instead, I think that libraries that have status codes should add their own tags - e.g. "grpc.status_code".
Errors
As far as I understand, the OpenTracing "error" tag is for "Exception"-like cases, yes. Most OpenTracing instrumentation I've seen adds the "Exception" details via a span.Log( "event"="error", "error.object"=exception )
call (pseudo code).
Of course, the question is, whether this a case of the "log & throw" anti-pattern. If there's a parent span/activity it might log the same exception and re-throw again.
Not sure what the best solution is here. The instrumentation code could only set the "error" tag and not log/raise the exception. This means it would rely on some parent span/activity do log it but what if there isn't one? Also, it might make debugging harder as the exception details are associated with an activity/span that did not raise it. Imagine there's a retry-scenario where something fails twice and succeeds on the third attempt. Who's responsible for logging the exceptions?
There's also a difference between OpenTracing and Activity because with OpenTracing's "Log" concept, the exception always stays with the span whereas this is more decoupled in Activity
where it currently can only be done via a separate DiagnosticSource event.
LibraryInstrumentationGuidance.md
Outdated
} | ||
finally | ||
{ | ||
// stop activity if started | ||
if (activity != null) | ||
source.StopActivity(activity, new { Input = input, Output = output }); | ||
source.StopActivity(activity, new MyStopPayload { Input = input, Output = output }); |
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.
We should have a discussion about whether we should guide people to use explicit payload classes. They can improve efficiency and ease of use on the subscriber side, HOWEVER, they also are a mildly painful to use and do couple the receiver to the exact version of the sender. We do have 'fast' reflection APIs (see PropertyFetch class in DIagnosticSourceEventSource) that we can publically expose. These are 'pretty fast (< 10 instructions to fetch), and are probably good enough.
If that is not sufficient, and we really want to recommend everyone making their own types, you should include the definition of MyStopPayload in the example and we need give guidance on naming (the type should live in the YOUR_COMPONENT.DiagnosticSource namespace) and we need to describe versioning, and the fact that using Properties (not fields) is better.
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.
So we had a bit of discussion about it with @NickCraver in StackExchange/StackExchange.Redis#833 and https://github.com/dotnet/corefx/issues/18114.
I believe his concerns are 1) hard to use 2) no contract and compile time checks.
I agree with the first one, but not the second: we have to keep events and anonymous payloads backward compatible anyway and in reality for end-user application there is no compile time check.
It seems we do recommend using explicit types in DiagnosticListnener guide already.
Property fetching: we have Microsoft.Extensions.DiagnosticAdapter and while it does efficient payload parsing, it does not play well with Activities/sampling scenarios and deprecated events.
So we should either invest in DiagnosticAdapter or deprecate it and expose Property Fetcher.
Anyway, I agree we need more info about naming/versioning. I will add guidance from DiagnosticListnener guide. Eventually we can convert this doc +diagnostic listener + activity guide to several articles on docs.ms.com
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.
It would be great if "PropertyFetcher" became a first class citizen. I tried the DiagnosticAdapter project but I removed it again because I thought it was too complicated and of limited use. Using a switch
on the event names and the PropertyFetcher stuff isn't much less readable.
LibraryInstrumentationGuidance.md
Outdated
|
||
### Context Propagation | ||
|
||
In distributed systems, one user operation may be processed by many applications. To trace operation end-to-end, some context must be propagated along with external call. |
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 needs more meat. Basically an example (or leave it out, because as it is it does not help anyone).
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.
You know my concerns about having hard-coded header names etc. in instrumentation code. We should have a proper discussion about this at some point. Until then, "context propagation" should not be documented IMO.
Thanks @lmolkova for driving this. I agree with the general community that we need more investment in getting good instrumentation Are we saying that we have some examples of this now in Azure code? If it is open source, we should put the urls here as examples are useful (and I would be interested in seeing them). |
LibraryInstrumentationGuidance.md
Outdated
|
||
```csharp | ||
// create DiagnosticListener with unique name | ||
static DiagnosticListener source = new DiagnosticListener("Example.MyLibrary"); |
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 static variable should be explained in a separate code block IMO to better differentiate it from the actual instrumentation code.
LibraryInstrumentationGuidance.md
Outdated
**CONSIDER** providing more context via 'IsEnabled(OperationName, Input)' overload instaed of 'IsEnabled(OperationName)' | ||
In some cases tracing system needs more context to filter operations. A good example is tracing system that tracks HTTP calls and sends telemetry over HTTP as well. To avoid recursive tracing and save performance tracing system want to prevent such calls to be instrumented at all based on some request properties. | ||
|
||
**CONSIDER** firing 'MyOperation.Start' event |
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.
Firing the "Start" event should be a MUST in my opinion. Having only some libraries that do this is a complete mess. It also completely breaks adapters that must do stuff on every activity start event.
LibraryInstrumentationGuidance.md
Outdated
activity = new Activity("Example.MyLibrary.MyOperation"); | ||
source.StartActivity(activity, new { Input = input }); | ||
activity = new Activity("MyOperation"); | ||
activity.Start(); |
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.
See other comment. Firing the "Start"-event should be a MUST. It's a complete mess if this is optional and only done by a few libraries.
LibraryInstrumentationGuidance.md
Outdated
activity = new Activity("Example.MyLibrary.MyOperation"); | ||
source.StartActivity(activity, new { Input = input }); | ||
activity = new Activity("MyOperation"); | ||
activity.Start(); |
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.
Having "activity.Start()" and "source.StartActivity()" is very confusing anyway and I too think that this should be simplified somehow in the API.
LibraryInstrumentationGuidance.md
Outdated
} | ||
catch (Exception ex) | ||
{ | ||
activity?.AddTag("error", true); |
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.
Status code
OpenTracing currently only defines the "http.status_code" header and no non-http status codes. As there's no real protocol-independent standard (or none that I know of), I doubt that it makes sense to have a generic "status" tag/property. Instead, I think that libraries that have status codes should add their own tags - e.g. "grpc.status_code".
Errors
As far as I understand, the OpenTracing "error" tag is for "Exception"-like cases, yes. Most OpenTracing instrumentation I've seen adds the "Exception" details via a span.Log( "event"="error", "error.object"=exception )
call (pseudo code).
Of course, the question is, whether this a case of the "log & throw" anti-pattern. If there's a parent span/activity it might log the same exception and re-throw again.
Not sure what the best solution is here. The instrumentation code could only set the "error" tag and not log/raise the exception. This means it would rely on some parent span/activity do log it but what if there isn't one? Also, it might make debugging harder as the exception details are associated with an activity/span that did not raise it. Imagine there's a retry-scenario where something fails twice and succeeds on the third attempt. Who's responsible for logging the exceptions?
There's also a difference between OpenTracing and Activity because with OpenTracing's "Log" concept, the exception always stays with the span whereas this is more decoupled in Activity
where it currently can only be done via a separate DiagnosticSource event.
LibraryInstrumentationGuidance.md
Outdated
} | ||
finally | ||
{ | ||
// stop activity if started | ||
if (activity != null) | ||
source.StopActivity(activity, new { Input = input, Output = output }); | ||
source.StopActivity(activity, new MyStopPayload { Input = input, Output = output }); |
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.
It would be great if "PropertyFetcher" became a first class citizen. I tried the DiagnosticAdapter project but I removed it again because I thought it was too complicated and of limited use. Using a switch
on the event names and the PropertyFetcher stuff isn't much less readable.
|
||
**DO** make `DiagnosticListener` name globally unique. | ||
Every DiagnosticListener has a name. Tracing system discovers all available sources and subscribes to them based on the name. | ||
[DiagnosticSource guide](https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/DiagnosticSourceUsersGuide.md#naming-conventions) provides more naming considerations. |
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.
As I've said in #913, this document should be merged with the existing UserGuides.
LibraryInstrumentationGuidance.md
Outdated
|
||
### Context Propagation | ||
|
||
In distributed systems, one user operation may be processed by many applications. To trace operation end-to-end, some context must be propagated along with external call. |
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.
You know my concerns about having hard-coded header names etc. in instrumentation code. We should have a proper discussion about this at some point. Until then, "context propagation" should not be documented IMO.
…exception event added, to be continued
@lmolkova thank you for incorporating my feedback! |
LibraryInstrumentationGuidance.md
Outdated
**DO** Create one `DiagnosticListener` instance for a particular source (make it static): each time when new DiagnoscticListener is created, all subscribers are notified and about it. | ||
|
||
**DO** call 'IsEnabled()' for every operation. | ||
The first `source.IsEnabled()` call efficiently tell if there is any listener in the system for our source. |
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.
s/tell/tells maybe
LibraryInstrumentationGuidance.md
Outdated
|
||
**DO NOT** cache results of IsEnabled: tracing system may subscribe/unsubscribe at any moment. Results of IsEnabled are dynamic and depend on the context. | ||
|
||
**CONSIDER** providing more context via 'IsEnabled(OperationName, Input)' overload instaed of 'IsEnabled(OperationName)' |
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.
instead*
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.
I am OK with this guidance. There is the question of how people find it, but that should not block this checkin
I have not provided an update in a while... We are looking into providing common guidance for lib instrumentation later this year. We still recommend instrumenting .NET libraries with DiagnosticSource and Activity but want to have a consistent story in a polyglot environment, and we are still finalizing details. @ReubenBond thanks for the review. I'll fix the typos and merge it. @vancem thanks! there are a lot of TBDs in the guidance, so it's not really ready for docs.microsoft.com. I'll work on this once we finalize OpenCensus story. |
@lmolkova should we close this? |
Fix Issue #913.
Updating library instrumentation guidance per feedback.
This is a work in progress and there are many TODO/TBD items in the doc.
Consumer guide will be started separately.