Skip to content
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

System.Diagnostics.ActivitySource API Addition #40046

Closed
CodeBlanch opened this issue Jul 28, 2020 · 19 comments · Fixed by #40183
Closed

System.Diagnostics.ActivitySource API Addition #40046

CodeBlanch opened this issue Jul 28, 2020 · 19 comments · Fixed by #40183
Assignees
Labels
api-approved API was approved in API review, it can be implemented area-System.Diagnostics.Tracing
Milestone

Comments

@CodeBlanch
Copy link
Contributor

CodeBlanch commented Jul 28, 2020

Background and Motivation

I'm working on implementing the new System.Diagnostics.ActivitySource API using the OpenTelemetry-dotnet beta that was just released on top of it. I've run into a couple of pain points that I think an API tweak or two would help alleviate.

I'm consuming messages off a queue. These messages have W3C TraceParent and TraceState values stored as message headers. If I wanted to create an Activity using these values as a parent the API provides:

public sealed class ActivitySource
{
  public Activity? StartActivity(string name, ActivityKind kind, string parentId, IEnumerable<KeyValuePair<string, string?>>? tags = null, IEnumerable<ActivityLink>? links = null, DateTimeOffset startTime = default);
}

That would work fine but I don't want to create a parented Activity, I want to add this TraceContext as a link on that Activity.

ActivityLink only accepts ActivityContext in its ctor. There's actually no way I can find to parse a string TraceParent + TraceState into an ActivityContext. That functionality is available internally.

Proposed API

namespace System.Diagnostics
{
    public readonly partial struct ActivityContext : IEquatable<ActivityContext>
    {
+        public static bool TryParse(string w3cId, string? traceState, out ActivityContext context);
    }
}

What I'm looking for is a way to expose to users the internal parsing so I can turn my strings into a valid ActivityContext without a lot of additional work.

Workaround

What I'm doing right now is just implementing some of the internal logic myself...

		public static ActivityContext CreateActivityContextFromW3CTraceContext(string traceParent, string? traceState = null)
		{
			if (string.IsNullOrEmpty(traceParent))
				throw new ArgumentNullException(nameof(traceParent));

			if (traceParent.Length != 55)
				throw new FormatException($"TraceParent [{traceParent}] format is not supported.");

			ReadOnlySpan<char> traceIdSpan = traceParent.AsSpan(3, 32);
			ReadOnlySpan<char> spanIdSpan = traceParent.AsSpan(36, 16);

			return new ActivityContext(
				ActivityTraceId.CreateFromString(traceIdSpan),
				ActivitySpanId.CreateFromString(spanIdSpan),
				(ActivityTraceFlags)ConversionExtensions.HexByteFromChars(traceParent[53], traceParent[54]),
				traceState);
		}

/cc @tarekgh @noahfalk @cijothomas @eddynaka @alanwest

@CodeBlanch CodeBlanch added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Jul 28, 2020
@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added area-System.Diagnostics.Tracing untriaged New issue has not been triaged by the area owner labels Jul 28, 2020
@ghost
Copy link

ghost commented Jul 28, 2020

Tagging subscribers to this area: @tarekgh, @tommcdon, @pjanotti
See info in area-owners.md if you want to be subscribed.

@tarekgh tarekgh removed the untriaged New issue has not been triaged by the area owner label Jul 28, 2020
@tarekgh tarekgh added this to the 6.0.0 milestone Jul 28, 2020
@cijothomas
Copy link
Contributor

Similar ask: open-telemetry/opentelemetry-dotnet#934

@tarekgh
Copy link
Member

tarekgh commented Jul 28, 2020

This kind of nice to have but not really blocking as the workaround is really easy and simple.

The other workaround it to use reflection to call

internal static bool TryConvertIdToContext(string id, out ActivityContext context)
. or even you may copy the code from there.

This is pushed to next release we may look more if we need to expose it.

@CodeBlanch
Copy link
Contributor Author

You and I have very different views on what is easy and simple 🤣 The goal is to call the ctor on ActivityLink. Not something out of the norm or unexpected, right? Calling Activity.TryParseW3CTraceContext and passing the result would be easy and simple. The workaround means first understanding how Id/TraceParent is composed. Then going through the source to figure out the internal logic. Then stitching up a reflection call or reproducing the logic, which is calling other internal things like HexByteFromChars. I think anyone that has to do this will agree it's a lot of effort just to call the ctor that exists.

The proposal makes the API more usable with an easy and simple tweak 😉 Isn't that why we do the previews? Anyway please reconsider including this.

@tarekgh
Copy link
Member

tarekgh commented Jul 29, 2020

You and I have very different views on what is easy and simple

fair enough. I said simple because the work around is a couple of lines I am pasing below.

The proposal makes the API more usable with an easy and simple tweak 😉 Isn't that why we do the previews?

We are in the stage of considering the blocking issues or the issues that very difficult to achieve. I don't think this one is either of them.

The goal is to call the ctor on ActivityLink. Not something out of the norm or unexpected, right? Calling Activity.TryParseW3CTraceContext and passing the result would be easy and simple. The workaround means first understanding how Id/TraceParent is composed. Then going through the source to figure out the internal logic. Then stitching up a reflection call or reproducing the logic, which is calling other internal things like HexByteFromChars. I think anyone that has to do this will agree it's a lot of effort just to call the ctor that exists.

Here is the workaround I am mentioning which really simple and also creates a wrapper method to create ActivityContext from the Id.

        delegate bool TryConvertIdToContext(string w3cId, out ActivityContext context);
        public static bool CreateActivityContext(string w3cId, out ActivityContext context) =>
            return ((TryConvertIdToContext)Delegate.CreateDelegate(typeof(TryConvertIdToContext), null, typeof(Activity).GetMethod("TryConvertIdToContext", BindingFlags.NonPublic | BindingFlags.Static)))(w3cId, out context);
        // we can optimize the allocation by caching the delegate too :-)

Your calling code should be simple enough as if calling the ActivityContext constructor:

	CreateActivityContext("00-99d43cb30a4cdb4fbeee3a19c29201b0-e82825765f051b47-01", out ActivityContext context); // equivalent to the future constructor: ActivityContext context = new ActivityContext("00-99d43cb30a4cdb4fbeee3a19c29201b0-e82825765f051b47-01");
    // you can check the result if needed. In the future if we have ActivityContext constructor, this may throw and you'll try-catch that I guess.

You can do the same idea for ActivityLink if you like.

@CodeBlanch
Copy link
Contributor Author

We are in the stage of considering the blocking issues or the issues that very difficult to achieve. I don't think this one is either of them.

My closing statements I promise 😄 Thanks for indulging me.

I think it will block the adoption. Once this ActivitySource API drops our mission in OTel will be to go out and get library authors to use it to instrument their code. That job is going to be a lot harder without a really usable API. Imagine how well it is going to go when we tell the Confluent Kafka client team they need to add Activity links on message consumption but they will need drills reflection to do it.

PS: The above solution doesn't work fully if you also want TraceState in the ActivityContext. TryConvertIdToContext only handles the TraceParent. The solution I have in the description works but up to the user to implement their own "HexToByte" extension.

@tarekgh
Copy link
Member

tarekgh commented Jul 29, 2020

My closing statements I promise 😄 Thanks for indulging me.

Your feedback is always valuable. we are discussing here the importance of this API. I am seeing the API is useful and helpful, only I am trying to know if it is urgent for this release or can wait. Anyway, please keep replying expressing your opinion. We'll discuss this issue and get back to you.

@alanwest
Copy link
Contributor

Just to chiming in with my two-cents here...

I'm with @CodeBlanch on the importance of this API given that the concept of links between spans has become an integral part of the OpenTelemetry specification. At this point I can't say I'm sure which vendors have backend support for links yet, though it will naturally be on their roadmaps. I work for New Relic, and at the moment New Relic does not yet have a way for visualizing links, though discussions are definitely underway. I have to imagine this is true for other vendors. ApplicationInsights maybe?

I'm definitely excited about the fact that .NET has embraced OTel and is baking its concepts directly into native .NET APIs, but with OpenTelemetry being pretty cutting edge, it does mean that things are moving and changing rapidly. Given that 1) links seem pretty well established in the OpenTelemetry specification and 2) .NET has already made the effort to encompass the concept as an ActivityLink, it seems like this suggestion is lower risk. Maybe?

Thanks for all your hard work! It's really cool seeing all this come together.

@CodeBlanch
Copy link
Contributor Author

I think where it will be really important is messaging clients. Adding a "follows-from" type of link to the original TraceParent. That's what I'm working on right now where I ran into the gap. The spec also talks about scatter/gather pattern and batching cases.

@tarekgh tarekgh modified the milestones: 6.0.0, 5.0.0 Jul 29, 2020
@tarekgh
Copy link
Member

tarekgh commented Jul 29, 2020

We have discussed internally and we'll look at getting this in 5.0. Here is the suggestion of the APIs

namespace System.Diagnostics
{
    public readonly partial struct ActivityContext : IEquatable<ActivityContext>
    {
        public bool TryParse(string w3cId, string? traceState, out ActivityContext context);
    }
}

The reason preferring this to avoid throwing in the constructor which make the caller code ugly.

Also, if we provided this, should be enough to parse and create ActivityLink. So, ActivityLink proposed API could be not needed at least for now.

Please let me know what you think as soon as you can. thanks for your feedback.

@tarekgh tarekgh added api-needs-work API needs work before it is approved, it is NOT ready for implementation and removed api-suggestion Early API idea and discussion, it is NOT ready for implementation labels Jul 29, 2020
@CodeBlanch
Copy link
Contributor Author

@tarekgh LGTM

@tarekgh
Copy link
Member

tarekgh commented Jul 29, 2020

Thanks @CodeBlanch for your quick reply. do you mind updating the the issue description with the proposal? we should be ok to schedule this for the design review after that.

@CodeBlanch
Copy link
Contributor Author

@tarekgh Done. I made it static in the proposal. @cijothomas pointed that out to me, it seemed like an oversight. Let me know if you want me to switch to instance-based.

@tarekgh
Copy link
Member

tarekgh commented Jul 29, 2020

@CodeBlanch good catch. Thanks a lot. Looks good to me. We should be good to proceed with that.

@tarekgh tarekgh added api-ready-for-review API is ready for review, it is NOT ready for implementation blocking Marks issues that we want to fast track in order to unblock other important work and removed api-needs-work API needs work before it is approved, it is NOT ready for implementation labels Jul 29, 2020
@terrajobst terrajobst removed api-ready-for-review API is ready for review, it is NOT ready for implementation blocking Marks issues that we want to fast track in order to unblock other important work labels Jul 30, 2020
@terrajobst terrajobst added the api-approved API was approved in API review, it can be implemented label Jul 30, 2020
@terrajobst
Copy link
Member

terrajobst commented Jul 30, 2020

Video

  • We should rename w3cid to traceParent
  • We considered offering a span-based overload but right now the most common case for the use of the API starts with a header value which is currently only ever going to be a string. If the networking layer will give access in a span-based fashion we can add it.
  • We should have a corresponding Parse method
namespace System.Diagnostics
{
    public readonly partial struct ActivityContext : IEquatable<ActivityContext>
    {
        public static ActivityContext Parse(string traceParent, string? traceState);
        public static bool TryParse(string traceParent, string? traceState, out ActivityContext context);
    }
}

@CodeBlanch
Copy link
Contributor Author

@tarekgh May I take a stab at a PR for this?

@tarekgh
Copy link
Member

tarekgh commented Jul 31, 2020

@CodeBlanch I am already finishing it. Sorry, if I know you are interested in doing it I would left it for you.

@CodeBlanch
Copy link
Contributor Author

@tarekgh No prob! Thanks for getting this in.

@tarekgh
Copy link
Member

tarekgh commented Aug 3, 2020

@ghost ghost locked as resolved and limited conversation to collaborators Dec 8, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented area-System.Diagnostics.Tracing
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants