-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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 ActivitySource.CreateActivity methods #48374
Add ActivitySource.CreateActivity methods #48374
Conversation
Note regarding the This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, to please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change. |
activity.StartTimeUtc = startTime.UtcDateTime; | ||
} | ||
|
||
if (startIt) |
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.
Does anything prevent us from doing?
if (startIt) | |
if (startIt) | |
{ | |
Start(); | |
} |
It feels odd to be replicating all this start code.
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 think it is possible. The only different case would be when someone calling StartActivity and passing ActivityContext with a default span Id. we used to keep Activity.Parent=null
but if we call Activity.Start()
it would set Activity.Parent
to the Activity.Current. I am not sure though if this will be ok (or should be the right behavior). what you think?
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.
In theory spanId = 0 is an invalid parent context. We should probably take a peak at the spec and chat with the OT folks to see if they have any input on what error handling behavior is desirable. I don't recall us ever defining the behavior very precisely but what I see the code currently do is give the bad id (in string or ActivityContext form) to the sampler as-is. Later in CreateAndStart we potentially ignore the id (if it was in string form) and create a new id based on IdFormat. This runs afoul of our sampler TraceId == Activity.TraceId invariant the same as above.
I don't care too much about the factoring on its own, but I am glad thinking about it sussed out issues like this one : )
@@ -146,6 +146,9 @@ public sealed class ActivitySource : IDisposable | |||
public string Name { get { throw null; } } | |||
public string? Version { get { throw null; } } | |||
public bool HasListeners() { throw null; } | |||
public System.Diagnostics.Activity? CreateActivity(string name, System.Diagnostics.ActivityKind kind) { throw null; } | |||
public System.Diagnostics.Activity? CreateActivity(string name, System.Diagnostics.ActivityKind kind, System.Diagnostics.ActivityContext parentContext, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>>? tags = null, System.Collections.Generic.IEnumerable<System.Diagnostics.ActivityLink>? links = null) { throw null; } | |||
public System.Diagnostics.Activity? CreateActivity(string name, System.Diagnostics.ActivityKind kind, string parentId, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>>? tags = null, System.Collections.Generic.IEnumerable<System.Diagnostics.ActivityLink>? links = null) { throw null; } |
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.
Weren't we going to handle #43853 by adding an IdFormat parameter here?
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.
No. After discussing this in the design review, @pakrym felt the other proposal is not needed and that can be handled by this CreateActivity proposal. After creating the activity, they can set the IdFormat on such created Activity before starting it.
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.
Unforetunately that doesn't work. CreateActivity() calls the sampling callback, sampling callback has access to the ID, generating that ID has already fixed the id format. Using SetIdFormat() after CreateActivity() should probably throw an exception if it doesn't already.
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.
public Activity SetIdFormat(ActivityIdFormat format)
{
if (_id != null || _spanId != null)
{
NotifyError(new InvalidOperationException(SR.SetFormatOnStartedActivity));
}
else
{
IdFormat = format;
}
return this;
}
If the Activity not started, _id
and _spanId
would still be nulls. otherwise Activity.Start() would fail too. no?
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.
@noahfalk I have addressed all comments. The remaining point is this one which related to IdFormat. I don't think we should fix the Ids before starting the Activity. Please let me know if you think otherwise.
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.
The caller is passing default context, there is no parent (Activity.Current), and the listeners didn't try to access the trace Id from the provided ActivityCreationOptions.
This case seems very real. Think of a worker app uploading a blob. There is no parent and all spans are exported so TraceId is not accessed.
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 case seems very real. Think of a worker app uploading a blob. There is no parent and all spans are exported so TraceId is not accessed.
Yes, agree. But using CreateActivity with the current design will make it work. right? I mean users can use CreateActivity and SetIdFormat if they need to for this scenario.
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.
@pakrym I had offline discussion with @noahfalk and we propose adding extra Boolean parameter to the CreateActivity called forceW3CId
. The reason we didn't have the parameter of ActivityIdFormat type because we want to promote W3C in general and not allow users to try the hierarchal explicitly. Please let's know if you have any concern. I'll send it anyway to the fxdc and will CC you there.
public Activity? CreateActivity(string name,
ActivityKind kind);
public Activity? CreateActivity(
string name,
ActivityKind kind,
ActivityContext parentContext,
IEnumerable<KeyValuePair<string, object?>>? tags = null,
IEnumerable<ActivityLink>? links = null,
bool forceW3CId = false); // added parameter
public Activity? CreateActivity(
string name,
ActivityKind kind,
string parentContext,
bool forceW3CId = false,
IEnumerable<KeyValuePair<string, object?>>? tags = null,
IEnumerable<ActivityLink>? links = null,
bool forceW3CId = false); // added parameter
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.
LGTM. If we want to promote W3C can we default the parameter to 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.
If we want to promote W3C can we default the parameter to true? 😄
I discussed that with @noahfalk and he preferred not to default the value to W3C as he want to have consistent behavior with StartActivity when passing the exact same parameters.
src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivitySourceTests.cs
Show resolved
Hide resolved
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.
Mostly looked fine but I think we need to fix the closure allocation
|
||
// delegate to ensure updating the trace id in other ActivityCreationOption object when a new trace Id is generated. | ||
// It is important so all listeners callbacks will always see the same trace Id which the activity will get created with | ||
Action<ActivityTraceId, bool> updateTraceId = (traceId, isChangedFromContext) => |
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 delegate needs a closure so it will do an allocation that we don't want. However we have a few ways out:
- Previously the behavior for ActivityCreationOptions<string>.TraceId is that it always returns default. I suggest we preserve that behavior so there is no need to copy the TraceId across the different contexts.
- Previously generating the TraceId was expensive because our random number generator was slow. Maybe now it is fast enough that there is little advantage in trying to generate it lazily? We'd need a benchmark measurement to know for sure.
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.
Ok, I'll go with the #1 to make ActivityCreationOptions<string>.TraceId
return default. I'll remove the delegate from the picture.
// - If the parentId string is null and parent ActivityContext is default and we have Activity.Current != null, then use Activity.Current.IdFormat. Otherwise, goto next step. | ||
// - If we have non default parent ActivityContext, then use W3C. Otherwise, goto next step. | ||
// - if we have non null parent id string, try to parse it to W3C. If parsing succeeded then use W3C format. otherwise use Hierarchical format. | ||
// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
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.
There is a missing case in the algorithm where we should choose to use default format id:
- IdFormat passed to ActivitySource.Create is Unknown (step 1 doesn't apply)
- ForceDefaultFormat = false (step 2 doesn't apply)
- Activity.Current = null (step 3 doesn't apply)
- parent ActivityContext/id are default (steps 4 and 5 don't apply)
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.
right. I handled this in the code :-) but didn't specify this step here. I'll add it here.
[InlineData($"Unknown, Unknown, W3C, null, true, false, W3C, W3C")] | ||
[InlineData($"Unknown, Unknown, Hierarchical, null, true, false, Hierarchical, Hierarchical")] | ||
|
||
[InlineData($"Unknown, Unknown, Unknown, NonWC3Id, true, false, Hierarchical, Hierarchical")] |
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.
Nit - the constants were inconsistent in the column:
[InlineData($"Unknown, Unknown, Unknown, NonWC3Id, true, false, Hierarchical, Hierarchical")] | |
[InlineData($"Unknown, Unknown, Unknown, NonWC3, true, false, Hierarchical, Hierarchical")] |
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.
will fix that.
// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ||
|
||
// ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ||
// Default Id Format, Id fromat to force, Parent id format, parent id, default parent context, validate trace Id, expected create format, expected start format |
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.
// Default Id Format, Id fromat to force, Parent id format, parent id, default parent context, validate trace Id, expected create format, expected start format | |
// Activity.DefaultIdFormat, Activity.Create(idFormat), Activity.Current.IdFormat, parent id, default parent context, validate trace Id, expected create format, expected start format | |
// (ForceIdFormat=true) |
I got the wrong idea about what these columns meant until I reverse engineered it from the source. This might help clarify the intent.
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.
will do
|
||
using ActivityListener listener = new ActivityListener(); | ||
listener.ShouldListenTo = (activitySource) => object.ReferenceEquals(aSource, activitySource); | ||
listener.SampleUsingParentId = (ref ActivityCreationOptions<string> activityOptions) => |
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.
Lets add a 2nd listener that only implements the Sample callback. This will let us confirm that Sample is always called for W3C formatted activities and never called for Hierarchal ones.
[InlineData($"Unknown, Hierarchical, Unknown, null, false, false, Hierarchical, W3C")] | ||
[InlineData($"Unknown, W3C, Unknown, null, false, false, W3C, W3C")] | ||
|
||
[InlineData($"Unknown, W3C, Unknown, null, true, true, W3C, Default")] |
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 the test case where a non-W3C string id gets passed as the parentId, the child does get W3C format, and the ActivityCreationOptions<ActivityContext>.TraceId property does get viewed inside the Sample callback. That case wasn't previously calling Sample and we need to ensure that now it does work correctly.
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 mean when the listener is not providing SampleUsingParentId
. right?
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.
Right!
listener.SampleUsingParentId = (ref ActivityCreationOptions<string> activityOptions) => | ||
{ | ||
if (checkTraceId) | ||
traceId = activityOptions.TraceId; |
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.
Assuming we keep the previous behavior for SampleWithParentId, activityOptions.TraceId should always return default here.
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.
To clarify, it will return default only in the case the parent Id string cannot parsed to W3C. right?
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 was looking at the code more closely and may have misinterpretted the old behavior, trying to review more closely now : )
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 did misinterpret and regardless I think the way you changed it will be fine. The behavior of ActivityCreationOptions<string>.TraceId is a bit weird, but this matches it's previous weird behavior and breaking change isn't necessarily any better : )
[InlineData($"Unknown, Unknown, Unknown, null, false, true, W3C, W3C")] | ||
|
||
[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] | ||
public void TestIdFormats(string data) |
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.
Nice test : )
@noahfalk I believe I have addressed all your feedback with the last commit. please let me know if you have any more comments or we good to go. Thanks! |
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.
LGTM, thanks Tarek!
Fixes #42784
This PR should be addressing #43853 too.