Skip to content
This repository was archived by the owner on Aug 3, 2024. It is now read-only.

Commit bfe443d

Browse files
Work-around heartbeat interval initialization bug in AI (#321)
* Work-around heartbeat interval initialization bug in AI * Use TelemetryConfiguration.CreateDefault() to account for applicationinsights.config file * Avoid static state. Added comments. Make DiagnosticsTelemetryModule accessible to allow tweaking heartbeats.
1 parent 01f9707 commit bfe443d

5 files changed

Lines changed: 136 additions & 67 deletions

File tree

src/NuGet.Services.Logging/ApplicationInsights.cs

Lines changed: 58 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -5,98 +5,89 @@
55
using Microsoft.ApplicationInsights;
66
using Microsoft.ApplicationInsights.DataContracts;
77
using Microsoft.ApplicationInsights.Extensibility;
8-
using Microsoft.ApplicationInsights.Extensibility.Implementation;
98
using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing;
109

1110
namespace NuGet.Services.Logging
1211
{
12+
/// <summary>
13+
/// Utility class to initialize an <see cref="ApplicationInsightsConfiguration"/> instance
14+
/// using provided instrumentation key, optional heartbeat interval,
15+
/// and, if detected, taking into account an optional ApplicationInsights.config file.
16+
/// </summary>
17+
/// <remarks>
18+
/// Calling <see cref="Initialize(string)"/> or <see cref="Initialize(string, TimeSpan)"/> returns the
19+
/// initialized <see cref="ApplicationInsightsConfiguration"/> object;
20+
/// it does not set the obsolete <see cref="TelemetryClient.Active"/> component.
21+
///
22+
/// It is the caller's responsibility to ensure the returned configuration is used
23+
/// when creating new <see cref="TelemetryClient"/> instances.
24+
/// </remarks>
1325
public static class ApplicationInsights
1426
{
15-
public static IHeartbeatPropertyManager HeartbeatManager { get; private set; }
16-
17-
public static bool Initialized { get; private set; }
18-
19-
public static void Initialize(string instrumentationKey)
27+
/// <summary>
28+
/// Initializes an <see cref="ApplicationInsightsConfiguration"/> using the provided
29+
/// <paramref name="instrumentationKey"/>, taking into account the <c>ApplicationInsights.config</c> file if present.
30+
/// </summary>
31+
/// <param name="instrumentationKey">The instrumentation key to use.</param>
32+
public static ApplicationInsightsConfiguration Initialize(string instrumentationKey)
2033
{
21-
InitializeTelemetryConfiguration(instrumentationKey, heartbeatInterval: null);
34+
return InitializeApplicationInsightsConfiguration(instrumentationKey, heartbeatInterval: null);
2235
}
2336

24-
public static void Initialize(string instrumentationKey, TimeSpan heartbeatInterval)
37+
/// <summary>
38+
/// Initializes an <see cref="ApplicationInsightsConfiguration"/> using the provided
39+
/// <paramref name="instrumentationKey"/> and <paramref name="heartbeatInterval"/>,
40+
/// taking into account the <c>ApplicationInsights.config</c> file if present.
41+
/// </summary>
42+
/// <param name="instrumentationKey">The instrumentation key to use.</param>
43+
/// <param name="heartbeatInterval">The heartbeat interval to use.</param>
44+
public static ApplicationInsightsConfiguration Initialize(
45+
string instrumentationKey,
46+
TimeSpan heartbeatInterval)
2547
{
26-
InitializeTelemetryConfiguration(instrumentationKey, heartbeatInterval);
48+
return InitializeApplicationInsightsConfiguration(instrumentationKey, heartbeatInterval);
2749
}
2850

29-
private static void InitializeTelemetryConfiguration(string instrumentationKey, TimeSpan? heartbeatInterval)
51+
private static ApplicationInsightsConfiguration InitializeApplicationInsightsConfiguration(
52+
string instrumentationKey,
53+
TimeSpan? heartbeatInterval)
3054
{
55+
// Note: TelemetryConfiguration.Active is being deprecated
56+
// https://github.com/microsoft/ApplicationInsights-dotnet/issues/1152
57+
// We use TelemetryConfiguration.CreateDefault() as opposed to instantiating a new TelemetryConfiguration()
58+
// to take into account the ApplicationInsights.config file (if detected).
59+
var telemetryConfiguration = TelemetryConfiguration.CreateDefault();
60+
3161
if (!string.IsNullOrWhiteSpace(instrumentationKey))
3262
{
33-
TelemetryConfiguration.Active.InstrumentationKey = instrumentationKey;
34-
TelemetryConfiguration.Active.TelemetryInitializers.Add(new TelemetryContextInitializer());
63+
telemetryConfiguration.InstrumentationKey = instrumentationKey;
64+
}
3565

36-
// Construct a TelemetryClient to emit traces so we can track and debug AI initialization.
37-
var telemetryClient = new TelemetryClient();
66+
telemetryConfiguration.TelemetryInitializers.Add(new TelemetryContextInitializer());
3867

39-
// Configure heartbeat interval if specified.
40-
// When not defined or null, the DiagnosticsTelemetryModule will use its internal defaults (heartbeat enabled, interval of 15 minutes).
41-
if (heartbeatInterval.HasValue)
42-
{
43-
var heartbeatManager = GetHeartbeatPropertyManager(telemetryClient);
44-
if (heartbeatManager != null)
45-
{
46-
heartbeatManager.HeartbeatInterval = heartbeatInterval.Value;
68+
// Construct a TelemetryClient to emit traces so we can track and debug AI initialization.
69+
var telemetryClient = new TelemetryClient(telemetryConfiguration);
4770

48-
telemetryClient.TrackTrace(
49-
$"Telemetry initialized using configured heartbeat interval: {heartbeatInterval.Value}.",
50-
SeverityLevel.Information);
51-
}
52-
}
53-
else
54-
{
55-
telemetryClient.TrackTrace(
56-
"Telemetry initialized using default heartbeat interval.",
71+
telemetryClient.TrackTrace(
72+
$"TelemetryConfiguration initialized using instrumentation key: {instrumentationKey ?? "EMPTY"}.",
5773
SeverityLevel.Information);
58-
}
5974

60-
Initialized = true;
61-
}
62-
else
63-
{
64-
Initialized = false;
65-
}
66-
}
75+
var diagnosticsTelemetryModule = new DiagnosticsTelemetryModule();
6776

68-
private static IHeartbeatPropertyManager GetHeartbeatPropertyManager(TelemetryClient telemetryClient)
69-
{
70-
if (HeartbeatManager == null)
77+
// Configure heartbeat interval if specified.
78+
// When not defined, the DiagnosticsTelemetryModule will use its internal defaults (heartbeat enabled, interval of 15 minutes).
79+
var traceMessage = "DiagnosticsTelemetryModule initialized using default heartbeat interval.";
80+
if (heartbeatInterval.HasValue)
7181
{
72-
var telemetryModules = TelemetryModules.Instance;
82+
diagnosticsTelemetryModule.HeartbeatInterval = heartbeatInterval.Value;
83+
traceMessage = $"DiagnosticsTelemetryModule initialized using configured heartbeat interval: {heartbeatInterval.Value}.";
84+
}
7385

74-
try
75-
{
76-
foreach (var module in telemetryModules.Modules)
77-
{
78-
if (module is IHeartbeatPropertyManager heartbeatManager)
79-
{
80-
HeartbeatManager = heartbeatManager;
81-
}
82-
}
83-
}
84-
catch (Exception hearbeatManagerAccessException)
85-
{
86-
// An non-critical, unexpected exception occurred trying to access the heartbeat manager.
87-
telemetryClient.TrackTrace(
88-
$"There was an error accessing heartbeat manager. Details: {hearbeatManagerAccessException.ToInvariantString()}",
89-
SeverityLevel.Error);
90-
}
86+
diagnosticsTelemetryModule.Initialize(telemetryConfiguration);
9187

92-
if (HeartbeatManager == null)
93-
{
94-
// Heartbeat manager unavailable: log warning.
95-
telemetryClient.TrackTrace("Heartbeat manager unavailable", SeverityLevel.Warning);
96-
}
97-
}
88+
telemetryClient.TrackTrace(traceMessage, SeverityLevel.Information);
9889

99-
return HeartbeatManager;
90+
return new ApplicationInsightsConfiguration(telemetryConfiguration, diagnosticsTelemetryModule);
10091
}
10192
}
10293
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Microsoft.ApplicationInsights.Extensibility;
6+
using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing;
7+
8+
namespace NuGet.Services.Logging
9+
{
10+
public sealed class ApplicationInsightsConfiguration
11+
{
12+
internal ApplicationInsightsConfiguration(
13+
TelemetryConfiguration telemetryConfiguration,
14+
DiagnosticsTelemetryModule diagnosticsTelemetryModule)
15+
{
16+
TelemetryConfiguration = telemetryConfiguration ?? throw new ArgumentNullException(nameof(telemetryConfiguration));
17+
DiagnosticsTelemetryModule = diagnosticsTelemetryModule ?? throw new ArgumentNullException(nameof(diagnosticsTelemetryModule));
18+
}
19+
20+
/// <summary>
21+
/// Contains the initialized <see cref="Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration"/>.
22+
/// Used to initialize new <see cref="Microsoft.ApplicationInsights.TelemetryClient"/> instances.
23+
/// Allows tweaking telemetry initializers.
24+
/// </summary>
25+
/// <remarks>
26+
/// Needs to be disposed when gracefully shutting down the application.
27+
/// </remarks>
28+
public TelemetryConfiguration TelemetryConfiguration { get; }
29+
30+
/// <summary>
31+
/// Contains the initialized <see cref="Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing.DiagnosticsTelemetryModule"/>.
32+
/// Allows tweaking Application Insights heartbeat telemetry.
33+
/// </summary>
34+
public DiagnosticsTelemetryModule DiagnosticsTelemetryModule { get; }
35+
}
36+
}

src/NuGet.Services.Logging/NuGet.Services.Logging.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
</ItemGroup>
5050
<ItemGroup>
5151
<Compile Include="ApplicationInsights.cs" />
52+
<Compile Include="ApplicationInsightsConfiguration.cs" />
5253
<Compile Include="DurationMetric.cs" />
5354
<Compile Include="ExceptionTelemetryProcessor.cs" />
5455
<Compile Include="LoggingSetup.cs" />
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Xunit;
6+
7+
namespace NuGet.Services.Logging.Tests
8+
{
9+
public class ApplicationInsightsTests
10+
{
11+
private const string InstrumentationKey = "abcdef12-3456-7890-abcd-ef123456789";
12+
13+
[Fact]
14+
public void InitializeReturnsApplicationInsightsConfiguration()
15+
{
16+
var applicationInsightsConfiguration = ApplicationInsights.Initialize(InstrumentationKey);
17+
18+
Assert.NotNull(applicationInsightsConfiguration);
19+
Assert.Equal(InstrumentationKey, applicationInsightsConfiguration.TelemetryConfiguration.InstrumentationKey);
20+
}
21+
22+
[Fact]
23+
public void InitializeRegistersTelemetryContextInitializer()
24+
{
25+
var applicationInsightsConfiguration = ApplicationInsights.Initialize(InstrumentationKey);
26+
Assert.Contains(applicationInsightsConfiguration.TelemetryConfiguration.TelemetryInitializers, ti => ti is TelemetryContextInitializer);
27+
}
28+
29+
[Fact]
30+
public void InitializeSetsHeartbeatIntervalAndDiagnosticsTelemetryModule()
31+
{
32+
var heartbeatInterval = TimeSpan.FromMinutes(1);
33+
34+
var applicationInsightsConfiguration = ApplicationInsights.Initialize(InstrumentationKey, heartbeatInterval);
35+
36+
Assert.NotNull(applicationInsightsConfiguration.DiagnosticsTelemetryModule);
37+
Assert.Equal(heartbeatInterval, applicationInsightsConfiguration.DiagnosticsTelemetryModule.HeartbeatInterval);
38+
}
39+
}
40+
}

tests/NuGet.Services.Logging.Tests/NuGet.Services.Logging.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
</PackageReference>
5454
</ItemGroup>
5555
<ItemGroup>
56+
<Compile Include="ApplicationInsightsTests.cs" />
5657
<Compile Include="ExceptionTelemetryProcessorTests.cs" />
5758
<Compile Include="LoggingSetupTests.cs" />
5859
<Compile Include="Properties\AssemblyInfo.cs" />

0 commit comments

Comments
 (0)