-
Notifications
You must be signed in to change notification settings - Fork 118
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
Telemetry Publishing via Activity & Activity Event #455
Conversation
...crosoft.FeatureManagement.Telemetry.ApplicationInsights/ApplicationInsightsEventPublisher.cs
Outdated
Show resolved
Hide resolved
...crosoft.FeatureManagement.Telemetry.ApplicationInsights/ApplicationInsightsEventPublisher.cs
Outdated
Show resolved
Hide resolved
|
||
activityListener = new ActivityListener | ||
{ | ||
ShouldListenTo = (activitySource) => activitySource.Name == "Microsoft.FeatureManagement", |
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 value "Microsoft.FeatureManagement"
is mentioned twice in different places. Consider extracting a proper constant for it that can be shared for type safety and refactor-friendliness.
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.
Good callout. The problem to me is that they're currently in different packages. One string is in the primary Microsoft.FeatureManagement
while the other is in Microsoft.FeatureManagement.Telemetry.ApplicationInsights
. I could expose it as a public or InternalsVisibleToAttribute to share between the packages but it didn't feel valuable enough.
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.
internal
+ InternalsVisibleTo
would be the best in this case indeed to avoid overexposing it. I see it is a bit convoluted though still, probably not worth it as you said.
...crosoft.FeatureManagement.Telemetry.ApplicationInsights/ApplicationInsightsEventPublisher.cs
Show resolved
Hide resolved
...rosoft.FeatureManagement.Telemetry.ApplicationInsights/FeatureManagementBuilderExtensions.cs
Outdated
Show resolved
Hide resolved
} | ||
} | ||
|
||
ActivityEvent activityEvent = new ActivityEvent("feature_flag", DateTimeOffset.Now, tags); |
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.
Consider evaluating the DateTimeOffset.Now
closer to the actual flag check and passing it as an argument so it reflects the actual moment a bit more accurately. By evaluating here, any added delay from the extra processing would end up influencing the recorded time.
Maybe grab it even before the various ISessionManager
calls (I don't know exactly what that abstraction is responsible for so take with a grain of salt).
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.
That would be more accurate for when the actual check started, but I think we'd rather be as close as possible to the return of the final result. Reason being so metrics emitted at or after this timestamp can be expected to use the new result. If we did it at the beginning and their implementation of ISessionManager
was slow, the timestamps would be less reliable to correlate.
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.
Although- I could use the end of the activity to get the timestamp I'm referring to, not sure if that would be as reliable though. But in that same line of thinking, the start of the activity could be the considered the start.
@@ -34,6 +34,7 @@ | |||
|
|||
<ItemGroup> | |||
<PackageReference Include="Microsoft.ApplicationInsights" Version="2.22.0" /> | |||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" /> |
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 refers Microsoft.Extensions.Logging
instead of the abstraction package in Microsoft.FeatureManagement
project.
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.
@zhiyuanliang-ms Microsoft.Extensions.Hosting.Abstractions
is required here because of the hosted service usage. If the project can reference just the Abstractions
package, that will generate less dependencies for consumers which is a good thing.
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 mean, in Microsoft.FeatureManagement.csproj
we referenceMicrosoft.Extensions.Logging
instead of Microsoft.Extensions.Logging.Abstraction`. we can change it to abstraction as well.
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.
Ah gotcha. If possible, that would make sense indeed, for the same reason.
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'll hold off on that change in this PR but agreed we should investigate.
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> | ||
public Task StartAsync(CancellationToken cancellationToken) | ||
{ | ||
appInsightsPublisher = new ApplicationInsightsEventPublisher(_telemetryClient); |
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.
Is there any reason why we do not follow the opentelemtry's implementation
This hosted service is aimed to ensure that the ApplicationInsightsEventPublisher
will be instantiated. Besides, we can take advantage of the DI system to manage the lifecycle of it.
Instead of injecting TelemetryClient
to the HostedService, IServiceProvider is injected and serviceProvider.GetService() is called.
AddAppInsightsTelemetryPublisher
method should also register ApplicationInsightsEventPublisher
as singleton.
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's a few things to talk about here- one, is that linked OpenTelemetry implementation is actually setting up their ActivitySources
, not ActivityListeners
. Their call to setup the listeners is a part of .WithTracing()
WithTracing
creates a few things, but it eventually constructs TracerProviderSdk
which creates the listeners during construction and explicitly has a shutdown function and a dispose function to handle disposing the listener: https://github.com/open-telemetry/opentelemetry-dotnet/blob/c38cc273bcd03949897d1585e9bc083962cb5f22/src/OpenTelemetry/Trace/TracerProviderSdk.cs#L352
However, we are instead using the IHostedService
to handle the "shutdown" (OnStop) and dispose options for our ActivityListeners. It appears I forgot this- because at some point after my design I ripped out IDisposable
on the IHostedService
- which means in its current state, this would not dispose correctly in a bad exit where OnStop isn't called. 😅
The main question is, who should dispose the publisher? The options to me are:
- IHostedService. If so- it should dispose it appropriately on both shutdown and when sp disposes the IHostedService.
- ServiceProvider. If so- the disposal will happen automatically which is clean. However- this means we also have an IHostedService that stays around in the background for no reason. Stopping the hosted service would do nothing and disposing it does nothing but remove it. There's also no clear way to undo the IHostedService start without doing a full disposal of the entire ServiceProvider.
So in short- it came down to preference. I ended up landing on 1, but there's been a bit of pushback about it, so I'll likely switch over to 2.
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 just need some hook to create the instance, could an option 3 using a IStartupFilter
implementation also work? That interface provides IServiceProvider
, is executed in the beginning of the pipeline, and doesn't "linger" like the hosted service would.
Contrary to HostedService, it also executes before the application actually starts listening to anything, which might actually be a positive 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.
IStartupFilter
would be a great fit here- the issue is that it's only available in ASP.NET Core. Currently this telemetry publishing is possible without any ASP.NET dependency.
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.
Ah you are absolutely right @rossgrambo , I honestly completely forgot about that important detail when suggesting it 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.
@rossgrambo I assume you meant that it is limited to Microsoft.Extensions.Hosting.Abstractions
v8
+. This doesn't seem to be tied to the framework but to the library itself. Supporting it would be a matter of bumping the dependency on Microsoft.FeatureManagement.Telemetry.ApplicationInsights
.
Regardless, I'm curious where you found that. The page for the interface says it is supported since v6:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostedlifecycleservice?view=net-8.0#applies-to
Can you elaborate?
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.
Oh I may have been mislead by the article then. The article linked earlier mentions it a couple times as a "new thing in .NET 8". Even the title says it's new in .NET 8. When searching for it- I saw the same mention of .NET 8 in other articles too.
But I just implemented it and ran it successfully with .net 6 and net48. So I suppose it's available?
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.
Oh okay I found the issue. The StartingAsync
and StoppingAsync
methods aren't called unless the project is running >= .NET8.
I could branch the logic on which .NET we're in- but I think I'll just stick with the HostedService
StartAsync
for now- unless there's a good way around this.
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.
*Updated to use service provider here as @zhiyuanliang-ms suggested
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.
Oh okay I found the issue. The StartingAsync and StoppingAsync methods aren't called unless the project is running >= .NET8.
Ohhh that is very weird! Documentation on this interface should be updated to reflect this limitation I think!
Sorry for leading you down this path @rossgrambo , I had no idea.
Co-authored-by: Juliano Leal Goncalves <[email protected]>
Co-authored-by: Juliano Leal Goncalves <[email protected]>
Co-authored-by: Juliano Leal Goncalves <[email protected]>
… creating the publisher
/// <returns>The feature management builder.</returns> | ||
public static IFeatureManagementBuilder AddAppInsightsTelemetryPublisher(this IFeatureManagementBuilder builder) | ||
{ | ||
if (builder == 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.
Could you add these null check for the WithTargeting
extension method? It is fine if we want to put it in a new PR.
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.
Let's do another PR- because I'm not even sure this null check makes sense. We're using an extension parameter, which I believe means this method couldn't even be called unless the instance existed.
...crosoft.FeatureManagement.Telemetry.ApplicationInsights/ApplicationInsightsEventPublisher.cs
Show resolved
Hide resolved
Co-authored-by: Zhiyuan Liang <[email protected]>
...crosoft.FeatureManagement.Telemetry.ApplicationInsights/ApplicationInsightsEventPublisher.cs
Outdated
Show resolved
Hide resolved
...crosoft.FeatureManagement.Telemetry.ApplicationInsights/ApplicationInsightsEventPublisher.cs
Outdated
Show resolved
Hide resolved
...rosoft.FeatureManagement.Telemetry.ApplicationInsights/FeatureManagementBuilderExtensions.cs
Outdated
Show resolved
Hide resolved
…com/microsoft/FeatureManagement-Dotnet into rossgrambo-activity-based-telemetry
…null or empty event field check
Thanks for the thorough review on this @julealgon ! |
} | ||
} | ||
|
||
var activityEvent = new ActivityEvent("feature_flag", DateTimeOffset.UtcNow, tags); |
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.
Why does this use snake casing instead of pascal like all the other fields?
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 is following OpenTelemetry's guide for the naming and I didn't feel strongly to deviate. It made more sense when we were emitting both OpenTelemetry and FeatureManagement fields, but I still don't feel we need to deviate.
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 would be the only place where any external naming convention is used, and the naming deviates from the casing of all of our tags. Seems that it should be FeatureFlag
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.
Makes sense. PR: #464
Why this PR?
#412
We now have our own
ActivitySource
and add anActivityEvent
on it. The event is essentially theEvaluationEvent
that was previously passed to the Publisher.Why our own
ActivitySource
instead of using the parentActivity
?We could emit the ActivityEvent onto whatever parent Activity is running. Sadly this approach has a few drawbacks:
Visible Changes
Becomes
ApplicationInsightsTelemetryPublisher
&ITelemetryPublisher
have been removed entirely