-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Minimal work to make System.Diagnostics.Activity usable by the dotnet CLI application and codebase #49749
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
base: main
Are you sure you want to change the base?
Minimal work to make System.Diagnostics.Activity usable by the dotnet CLI application and codebase #49749
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Diagnostics; | ||
|
||
namespace Microsoft.DotNet.Cli.Utils; | ||
|
||
/// <summary> | ||
/// Contains helpers for working with <see cref="System.Diagnostics.Activity">Activities</see> in the .NET CLI. | ||
/// </summary> | ||
public static class Activities | ||
{ | ||
|
||
/// <summary> | ||
/// The main entrypoint for creating <see cref="Activity">Activities</see> in the .NET CLI. | ||
/// All activities created in the CLI should use this <see cref="ActivitySource"/>, to allow | ||
/// consumers to easily filter and trace CLI activities. | ||
/// </summary> | ||
public static ActivitySource Source { get; } = new("dotnet-cli", Product.Version); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,7 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
#nullable disable | ||
|
||
using System.Collections.Frozen; | ||
using System.Diagnostics; | ||
using Microsoft.ApplicationInsights; | ||
using Microsoft.ApplicationInsights.Extensibility; | ||
|
@@ -14,27 +13,27 @@ namespace Microsoft.DotNet.Cli.Telemetry; | |
|
||
public class Telemetry : ITelemetry | ||
{ | ||
internal static string CurrentSessionId = null; | ||
internal static string? CurrentSessionId = null; | ||
internal static bool DisabledForTests = false; | ||
private readonly int _senderCount; | ||
private TelemetryClient _client = null; | ||
private Dictionary<string, string> _commonProperties = null; | ||
private Dictionary<string, double> _commonMeasurements = null; | ||
private Task _trackEventTask = null; | ||
private TelemetryClient? _client = null; | ||
private FrozenDictionary<string, string>? _commonProperties = null; | ||
private FrozenDictionary<string, double>? _commonMeasurements = null; | ||
private Task? _trackEventTask = null; | ||
|
||
private const string ConnectionString = "InstrumentationKey=74cc1c9e-3e6e-4d05-b3fc-dde9101d0254"; | ||
|
||
public bool Enabled { get; } | ||
|
||
public Telemetry() : this(null) { } | ||
|
||
public Telemetry(IFirstTimeUseNoticeSentinel sentinel) : this(sentinel, null) { } | ||
public Telemetry(IFirstTimeUseNoticeSentinel? sentinel) : this(sentinel, null) { } | ||
|
||
public Telemetry( | ||
IFirstTimeUseNoticeSentinel sentinel, | ||
string sessionId, | ||
IFirstTimeUseNoticeSentinel? sentinel, | ||
string? sessionId, | ||
bool blockThreadInitialization = false, | ||
IEnvironmentProvider environmentProvider = null, | ||
IEnvironmentProvider? environmentProvider = null, | ||
int senderCount = 3) | ||
{ | ||
|
||
|
@@ -78,7 +77,7 @@ internal static void EnableForTests() | |
DisabledForTests = false; | ||
} | ||
|
||
private static bool PermissionExists(IFirstTimeUseNoticeSentinel sentinel) | ||
private static bool PermissionExists(IFirstTimeUseNoticeSentinel? sentinel) | ||
{ | ||
if (sentinel == null) | ||
{ | ||
|
@@ -97,9 +96,17 @@ public void TrackEvent(string eventName, IDictionary<string, string> properties, | |
} | ||
|
||
//continue the task in different threads | ||
_trackEventTask = _trackEventTask.ContinueWith( | ||
x => TrackEventTask(eventName, properties, measurements) | ||
); | ||
if (_trackEventTask == null) | ||
{ | ||
_trackEventTask = Task.Run(() => TrackEventTask(eventName, properties, measurements)); | ||
return; | ||
} | ||
else | ||
{ | ||
_trackEventTask = _trackEventTask.ContinueWith( | ||
x => TrackEventTask(eventName, properties, measurements) | ||
); | ||
} | ||
} | ||
|
||
public void Flush() | ||
|
@@ -148,7 +155,7 @@ private void InitializeTelemetry() | |
_client.Context.Device.OperatingSystem = CLIRuntimeEnvironment.OperatingSystem; | ||
|
||
_commonProperties = new TelemetryCommonProperties().GetTelemetryCommonProperties(); | ||
_commonMeasurements = []; | ||
_commonMeasurements = FrozenDictionary<string, double>.Empty; | ||
} | ||
catch (Exception e) | ||
{ | ||
|
@@ -170,39 +177,84 @@ private void TrackEventTask( | |
|
||
try | ||
{ | ||
Dictionary<string, string> eventProperties = GetEventProperties(properties); | ||
Dictionary<string, double> eventMeasurements = GetEventMeasures(measurements); | ||
var eventProperties = GetEventProperties(properties); | ||
var eventMeasurements = GetEventMeasures(measurements); | ||
|
||
eventProperties ??= new Dictionary<string, string>(); | ||
eventProperties.Add("event id", Guid.NewGuid().ToString()); | ||
|
||
_client.TrackEvent(PrependProducerNamespace(eventName), eventProperties, eventMeasurements); | ||
Activity.Current?.AddEvent(CreateActivityEvent(eventName, eventProperties, eventMeasurements)); | ||
} | ||
catch (Exception e) | ||
{ | ||
Debug.Fail(e.ToString()); | ||
} | ||
} | ||
|
||
private static string PrependProducerNamespace(string eventName) | ||
private static ActivityEvent CreateActivityEvent( | ||
string eventName, | ||
IDictionary<string, string>? properties, | ||
IDictionary<string, double>? measurements) | ||
{ | ||
return "dotnet/cli/" + eventName; | ||
var tags = MakeTags(properties, measurements); | ||
return new ActivityEvent( | ||
PrependProducerNamespace(eventName), | ||
tags: tags); | ||
} | ||
|
||
private Dictionary<string, double> GetEventMeasures(IDictionary<string, double> measurements) | ||
private static ActivityTagsCollection? MakeTags( | ||
IDictionary<string, string>? properties, | ||
IDictionary<string, double>? measurements) | ||
{ | ||
Dictionary<string, double> eventMeasurements = new(_commonMeasurements); | ||
if (measurements != null) | ||
if (properties == null && measurements == null) | ||
{ | ||
foreach (KeyValuePair<string, double> measurement in measurements) | ||
{ | ||
eventMeasurements[measurement.Key] = measurement.Value; | ||
} | ||
return null; | ||
} | ||
else if (properties != null && measurements == null) | ||
{ | ||
return [.. properties.Select(p => new KeyValuePair<string, object?>(p.Key, p.Value))]; | ||
} | ||
else if (properties == null && measurements != null) | ||
{ | ||
return [.. measurements.Select(m => new KeyValuePair<string, object?>(m.Key, m.Value.ToString()))]; | ||
} | ||
else return [ .. properties!.Select(p => new KeyValuePair<string, object?>(p.Key, p.Value)), | ||
.. measurements!.Select(m => new KeyValuePair<string, object?>(m.Key, m.Value.ToString())) ]; | ||
} | ||
|
||
private static string PrependProducerNamespace(string eventName) => $"dotnet/cli/{eventName}"; | ||
|
||
private IDictionary<string, double>? GetEventMeasures(IDictionary<string, double>? measurements) | ||
{ | ||
if (measurements is null) | ||
{ | ||
return _commonMeasurements; | ||
} | ||
if (_commonMeasurements == null) | ||
{ | ||
return measurements; | ||
} | ||
Comment on lines
+234
to
+237
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
IDictionary<string, double> eventMeasurements = new Dictionary<string, double>(_commonMeasurements); | ||
foreach (KeyValuePair<string, double> measurement in measurements) | ||
{ | ||
eventMeasurements[measurement.Key] = measurement.Value; | ||
} | ||
return eventMeasurements; | ||
} | ||
|
||
private Dictionary<string, string> GetEventProperties(IDictionary<string, string> properties) | ||
private IDictionary<string, string>? GetEventProperties(IDictionary<string, string>? properties) | ||
{ | ||
if (properties is null) | ||
{ | ||
return _commonProperties; | ||
} | ||
Comment on lines
+249
to
+252
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Returning |
||
if (_commonProperties == null) | ||
{ | ||
return properties; | ||
} | ||
|
||
var eventProperties = new Dictionary<string, string>(_commonProperties); | ||
if (properties != 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.
[nitpick] The branching in
MakeTags
is repetitive. Consider consolidating the four branches into a single enumeration of properties and measurements to simplify the logic.Copilot uses AI. Check for mistakes.