-
Notifications
You must be signed in to change notification settings - Fork 287
/
TelemetryConfiguration.cs
523 lines (466 loc) · 22 KB
/
TelemetryConfiguration.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
namespace Microsoft.ApplicationInsights.Extensibility
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.Extensibility.Implementation;
using Microsoft.ApplicationInsights.Extensibility.Implementation.ApplicationId;
using Microsoft.ApplicationInsights.Extensibility.Implementation.Authentication;
using Microsoft.ApplicationInsights.Extensibility.Implementation.Endpoints;
using Microsoft.ApplicationInsights.Extensibility.Implementation.Sampling;
using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing;
using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing.SelfDiagnostics;
using Microsoft.ApplicationInsights.Metrics;
using Microsoft.ApplicationInsights.Metrics.Extensibility;
/// <summary>
/// Encapsulates the global telemetry configuration typically loaded from the ApplicationInsights.config file.
/// </summary>
/// <remarks>
/// All <see cref="TelemetryContext"/> objects are initialized using the <see cref="Active"/>
/// telemetry configuration provided by this class.
/// </remarks>
public sealed class TelemetryConfiguration : IDisposable
{
internal readonly SamplingRateStore LastKnownSampleRateStore = new SamplingRateStore();
private static object syncRoot = new object();
private static TelemetryConfiguration active;
private readonly SnapshottingList<ITelemetryInitializer> telemetryInitializers = new SnapshottingList<ITelemetryInitializer>();
private readonly TelemetrySinkCollection telemetrySinks = new TelemetrySinkCollection();
private TelemetryProcessorChain telemetryProcessorChain;
private string instrumentationKey = string.Empty;
private string connectionString;
private bool disableTelemetry = false;
private TelemetryProcessorChainBuilder builder;
private MetricManager metricManager = null;
private IApplicationIdProvider applicationIdProvider;
/// <summary>
/// Indicates if this instance has been disposed of.
/// </summary>
private bool isDisposed = false;
/// <summary>
/// Static Constructor which sets ActivityID Format to W3C if Format not enforced.
/// This ensures SDK operates in W3C mode, unless turned off explicitily with the following 2 lines
/// in user code in application startup.
/// Activity.DefaultIdFormat = ActivityIdFormat.Hierarchical
/// Activity.ForceDefaultIdFormat = true.
/// </summary>
static TelemetryConfiguration()
{
ActivityExtensions.TryRun(() =>
{
if (!Activity.ForceDefaultIdFormat)
{
Activity.DefaultIdFormat = ActivityIdFormat.W3C;
Activity.ForceDefaultIdFormat = true;
}
});
SelfDiagnosticsInitializer.EnsureInitialized();
}
/// <summary>
/// Initializes a new instance of the TelemetryConfiguration class.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public TelemetryConfiguration() : this(string.Empty, null)
{
}
/// <summary>
/// Initializes a new instance of the TelemetryConfiguration class.
/// </summary>
/// <param name="instrumentationKey">The instrumentation key this configuration instance will provide.</param>
public TelemetryConfiguration(string instrumentationKey) : this(instrumentationKey, null)
{
}
/// <summary>
/// Initializes a new instance of the TelemetryConfiguration class.
/// </summary>
/// <param name="instrumentationKey">The instrumentation key this configuration instance will provide.</param>
/// <param name="channel">The telemetry channel to provide with this configuration instance.</param>
public TelemetryConfiguration(string instrumentationKey, ITelemetryChannel channel)
{
this.instrumentationKey = instrumentationKey ?? throw new ArgumentNullException(nameof(instrumentationKey));
SetTelemetryChannelEndpoint(channel, this.EndpointContainer.FormattedIngestionEndpoint, force: true);
var defaultSink = new TelemetrySink(this, channel);
defaultSink.Name = "default";
this.telemetrySinks.Add(defaultSink);
}
/// <summary>
/// Gets the active <see cref="TelemetryConfiguration"/> instance loaded from the ApplicationInsights.config file.
/// If the configuration file does not exist, the active configuration instance is initialized with minimum defaults
/// needed to send telemetry to Application Insights.
/// </summary>
#if NETSTANDARD // This constant is defined for all versions of NetStandard https://docs.microsoft.com/en-us/dotnet/core/tutorials/libraries#how-to-multitarget
[Obsolete("We do not recommend using TelemetryConfiguration.Active on .NET Core. See https://github.com/microsoft/ApplicationInsights-dotnet/issues/1152 for more details")]
#endif
public static TelemetryConfiguration Active
{
get
{
if (active == null)
{
lock (syncRoot)
{
if (active == null)
{
active = new TelemetryConfiguration();
TelemetryConfigurationFactory.Instance.Initialize(active, TelemetryModules.Instance);
}
}
}
return active;
}
internal set
{
lock (syncRoot)
{
active = value;
}
}
}
/// <summary>
/// Gets or sets the default instrumentation key for the application.
/// </summary>
/// <exception cref="ArgumentNullException">The new value is null.</exception>
/// <remarks>
/// This instrumentation key value is used by default by all <see cref="TelemetryClient"/> instances
/// created in the application. This value can be overwritten by setting the <see cref="TelemetryContext.InstrumentationKey"/>
/// property of the <see cref="TelemetryClient.Context"/>.
/// </remarks>
public string InstrumentationKey
{
get { return this.instrumentationKey; }
set { this.instrumentationKey = value ?? throw new ArgumentNullException(nameof(this.InstrumentationKey)); }
}
/// <summary>
/// Gets or sets a value indicating whether sending of telemetry to Application Insights is disabled.
/// </summary>
/// <remarks>
/// This disable tracking setting value is used by default by all <see cref="TelemetryClient"/> instances
/// created in the application.
/// </remarks>
public bool DisableTelemetry
{
get
{
return this.disableTelemetry;
}
set
{
// Log the state of tracking
if (value)
{
CoreEventSource.Log.TrackingWasDisabled();
}
else
{
CoreEventSource.Log.TrackingWasEnabled();
}
this.disableTelemetry = value;
}
}
/// <summary>
/// Gets the list of <see cref="ITelemetryInitializer"/> objects that supply additional information about telemetry.
/// </summary>
/// <remarks>
/// Telemetry initializers extend Application Insights telemetry collection by supplying additional information
/// about individual <see cref="ITelemetry"/> items, such as <see cref="ITelemetry.Timestamp"/>. A <see cref="TelemetryClient"/>
/// invokes telemetry initializers each time <see cref="TelemetryClient.Track"/> method is called.
/// The default list of telemetry initializers is provided by the Application Insights NuGet packages and loaded from
/// the ApplicationInsights.config file located in the application directory.
/// </remarks>
public IList<ITelemetryInitializer> TelemetryInitializers
{
get { return this.telemetryInitializers; }
}
/// <summary>
/// Gets a readonly collection of TelemetryProcessors.
/// </summary>
public ReadOnlyCollection<ITelemetryProcessor> TelemetryProcessors
{
get
{
return new ReadOnlyCollection<ITelemetryProcessor>(this.TelemetryProcessorChain.TelemetryProcessors);
}
}
/// <summary>
/// Gets the TelemetryProcessorChainBuilder which can build and populate TelemetryProcessors in the TelemetryConfiguration.
/// </summary>
public TelemetryProcessorChainBuilder TelemetryProcessorChainBuilder
{
get
{
LazyInitializer.EnsureInitialized(ref this.builder, () => new TelemetryProcessorChainBuilder(this));
return this.builder;
}
internal set
{
this.builder = value;
}
}
/// <summary>
/// Gets or sets the telemetry channel for the default sink. Will also attempt to set the Channel's endpoint.
/// </summary>
public ITelemetryChannel TelemetryChannel
{
get
{
// We do not ensure not disposed here because TelemetryChannel is accessed during configuration disposal.
return this.telemetrySinks.DefaultSink.TelemetryChannel;
}
set
{
if (!this.isDisposed)
{
this.telemetrySinks.DefaultSink.TelemetryChannel = value;
SetTelemetryChannelEndpoint(this.telemetrySinks.DefaultSink.TelemetryChannel, this.EndpointContainer.FormattedIngestionEndpoint);
}
}
}
/// <summary>
/// Gets or sets the Application Id Provider.
/// </summary>
/// <remarks>
/// This feature is opt-in and must be configured to be enabled.
/// </remarks>
public IApplicationIdProvider ApplicationIdProvider
{
get
{
return this.applicationIdProvider;
}
set
{
this.applicationIdProvider = value;
SetApplicationIdEndpoint(this.applicationIdProvider, this.EndpointContainer.FormattedApplicationIdEndpoint);
}
}
/// <summary>
/// Gets the Endpoint Container responsible for making service endpoints available.
/// </summary>
public EndpointContainer EndpointContainer { get; private set; } = new EndpointContainer(new EndpointProvider());
/// <summary>
/// Gets or sets the connection string. Setting this value will also set (and overwrite) the <see cref="InstrumentationKey"/>. The endpoints are validated and will be set (and overwritten) for <see cref="InMemoryChannel"/> and ServerTelemetryChannel as well as the <see cref="ApplicationIdProvider"/>.
/// </summary>
public string ConnectionString
{
get
{
return this.connectionString;
}
set
{
try
{
this.connectionString = value ?? throw new ArgumentNullException(nameof(this.ConnectionString));
var endpointProvider = new EndpointProvider
{
ConnectionString = value,
};
this.InstrumentationKey = endpointProvider.GetInstrumentationKey();
this.EndpointContainer = new EndpointContainer(endpointProvider);
// UPDATE TELEMETRY CHANNEL
foreach (var tSink in this.TelemetrySinks)
{
SetTelemetryChannelEndpoint(tSink.TelemetryChannel, this.EndpointContainer.FormattedIngestionEndpoint, force: true);
}
// UPDATE APPLICATION ID PROVIDER
SetApplicationIdEndpoint(this.ApplicationIdProvider, this.EndpointContainer.FormattedApplicationIdEndpoint, force: true);
}
catch (Exception ex)
{
CoreEventSource.Log.ConnectionStringSetFailed(ex.ToInvariantString());
throw;
}
}
}
/// <summary>
/// Gets a collection of strings indicating if an experimental feature should be enabled.
/// The presence of a string in this collection will be evaluated as 'true'.
/// </summary>
/// <remarks>
/// This property allows the dev team to ship and evaluate features before adding these to the public API.
/// We are not committing to support any features enabled through this property.
/// Use this at your own risk.
/// </remarks>
[EditorBrowsable(EditorBrowsableState.Never)]
public IList<string> ExperimentalFeatures { get; } = new List<string>(0);
/// <summary>
/// Gets a list of telemetry sinks associated with the configuration.
/// </summary>
public IList<TelemetrySink> TelemetrySinks => this.telemetrySinks;
/// <summary>
/// Gets the default telemetry sink.
/// </summary>
public TelemetrySink DefaultTelemetrySink => this.telemetrySinks.DefaultSink;
/// <summary>
/// Gets an envelope for Azure.Core.TokenCredential which provides an AAD Authenticated token.
/// </summary>
internal ReflectionCredentialEnvelope CredentialEnvelope { get; private set; }
/// <summary>
/// Gets or sets the chain of processors.
/// </summary>
internal TelemetryProcessorChain TelemetryProcessorChain
{
get
{
if (this.telemetryProcessorChain == null)
{
this.TelemetryProcessorChainBuilder.Build();
}
return this.telemetryProcessorChain;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
this.telemetryProcessorChain = value;
}
}
/// <summary>
/// Creates a new <see cref="TelemetryConfiguration"/> instance loaded from the ApplicationInsights.config file.
/// If the configuration file does not exist, the new configuration instance is initialized with minimum defaults
/// needed to send telemetry to Application Insights.
/// </summary>
public static TelemetryConfiguration CreateDefault()
{
var configuration = new TelemetryConfiguration();
TelemetryConfigurationFactory.Instance.Initialize(configuration, null);
return configuration;
}
/// <summary>
/// Creates a new <see cref="TelemetryConfiguration"/> instance loaded from the specified configuration.
/// </summary>
/// <param name="config">An xml serialized configuration.</param>
/// <exception cref="ArgumentNullException">Throws if the config value is null or empty.</exception>
public static TelemetryConfiguration CreateFromConfiguration(string config)
{
if (string.IsNullOrWhiteSpace(config))
{
throw new ArgumentNullException(nameof(config));
}
var configuration = new TelemetryConfiguration();
TelemetryConfigurationFactory.Instance.Initialize(configuration, null, config);
return configuration;
}
/// <summary>
/// Releases resources used by the current instance of the <see cref="TelemetryConfiguration"/> class.
/// </summary>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Set a TokenCredential for this configuration.
/// </summary>
/// <param name="tokenCredential">An instance of Azure.Core.TokenCredential.</param>
public void SetCredential(object tokenCredential) => this.CredentialEnvelope = new ReflectionCredentialEnvelope(tokenCredential);
internal MetricManager GetMetricManager(bool createIfNotExists)
{
MetricManager manager = this.metricManager;
if (manager == null && createIfNotExists)
{
var pipelineAdapter = new ApplicationInsightsTelemetryPipeline(this);
MetricManager newManager = new MetricManager(pipelineAdapter);
MetricManager prevManager = Interlocked.CompareExchange(ref this.metricManager, newManager, null);
if (prevManager == null)
{
manager = newManager;
}
else
{
// We just created a new manager that we are not using. Stop is before discarding.
Task fireAndForget = newManager.StopDefaultAggregationCycleAsync();
manager = prevManager;
}
}
return manager;
}
/// <summary>
/// This will check the ApplicationIdProvider and attempt to set the endpoint.
/// This only supports our first party providers <see cref="ApplicationInsightsApplicationIdProvider"/> and <see cref="DictionaryApplicationIdProvider"/>.
/// </summary>
/// <param name="applicationIdProvider">ApplicationIdProvider to set.</param>
/// <param name="endpoint">Endpoint value to set.</param>
/// <param name="force">When the ConnectionString is set, ApplicationId Endpoint should be forced to update. If the ApplicationId has been set separately, we will only set endpoint if it is null.</param>
private static void SetApplicationIdEndpoint(IApplicationIdProvider applicationIdProvider, string endpoint, bool force = false)
{
if (applicationIdProvider != null)
{
if (applicationIdProvider is ApplicationInsightsApplicationIdProvider applicationInsightsApplicationIdProvider)
{
if (force || applicationInsightsApplicationIdProvider.ProfileQueryEndpoint == null)
{
applicationInsightsApplicationIdProvider.ProfileQueryEndpoint = endpoint;
}
}
else if (applicationIdProvider is DictionaryApplicationIdProvider dictionaryApplicationIdProvider)
{
if (dictionaryApplicationIdProvider.Next is ApplicationInsightsApplicationIdProvider innerApplicationIdProvider)
{
if (force || innerApplicationIdProvider.ProfileQueryEndpoint == null)
{
innerApplicationIdProvider.ProfileQueryEndpoint = endpoint;
}
}
}
}
}
/// <summary>
/// This will check the TelemetryChannel and attempt to set the endpoint.
/// This only supports our first party providers <see cref="InMemoryChannel"/> and ServerTelemetryChannel.
/// </summary>
/// <param name="channel">TelemetryChannel to set.</param>
/// <param name="endpoint">Endpoint value to set.</param>
/// /// <param name="force">When the ConnectionString is set, Channel Endpoint should be forced to update. If the Channel has been set separately, we will only set endpoint if it is null.</param>
private static void SetTelemetryChannelEndpoint(ITelemetryChannel channel, string endpoint, bool force = false)
{
if (channel != null)
{
if (channel is InMemoryChannel || channel.GetType().FullName == "Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.ServerTelemetryChannel")
{
if (force || channel.EndpointAddress == null)
{
channel.EndpointAddress = endpoint;
}
}
}
}
/// <summary>
/// Disposes of resources.
/// </summary>
/// <param name="disposing">Indicates if managed code is being disposed.</param>
private void Dispose(bool disposing)
{
if (!this.isDisposed && disposing)
{
this.isDisposed = true;
Interlocked.CompareExchange(ref active, null, this);
// I think we should be flushing this.telemetrySinks.DefaultSink.TelemetryChannel at this point.
// Filed https://github.com/Microsoft/ApplicationInsights-dotnet/issues/823 to track.
// For now just flushing the metrics:
this.metricManager?.Flush();
if (this.telemetryProcessorChain != null)
{
// Not setting this.telemetryProcessorChain to null because calls to the property getter would reinitialize it.
this.telemetryProcessorChain.Dispose();
}
foreach (TelemetrySink sink in this.telemetrySinks)
{
sink.Dispose();
if (!object.ReferenceEquals(sink, this.telemetrySinks.DefaultSink))
{
this.telemetrySinks.Remove(sink);
}
}
}
}
}
}