-
Notifications
You must be signed in to change notification settings - Fork 331
/
HttpTelemetryManager.cs
154 lines (132 loc) · 6.49 KB
/
HttpTelemetryManager.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
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Concurrent;
using System.Text;
using Microsoft.Identity.Client.TelemetryCore.Internal.Constants;
using Microsoft.Identity.Client.TelemetryCore.Internal.Events;
namespace Microsoft.Identity.Client.TelemetryCore.Http
{
/// <summary>
/// Responsible for recording API events and formatting CSV
/// with telemetry.
/// </summary>
/// <remarks>
/// Not fully thread safe - it is possible that multiple threads request
/// the "previous requests" data at the same time. It is the responsibility of
/// the caller to protect against this.
/// </remarks>
internal class HttpTelemetryManager : IHttpTelemetryManager
{
private int _successfulSilentCallCount = 0;
private ConcurrentQueue<ApiEvent> _failedEvents = new ConcurrentQueue<ApiEvent>();
public void ResetPreviousUnsentData()
{
_successfulSilentCallCount = 0;
while (_failedEvents.TryDequeue(out _))
{
// do nothing
}
}
public void RecordStoppedEvent(ApiEvent stoppedEvent)
{
if (!string.IsNullOrEmpty(stoppedEvent.ApiErrorCode))
{
_failedEvents.Enqueue(stoppedEvent);
}
// cache hits can occur in AcquireTokenSilent, AcquireTokenForClient and OBO
if (stoppedEvent.IsAccessTokenCacheHit)
{
_successfulSilentCallCount++;
}
}
/// <summary>
/// CSV expected format:
/// 3|silent_successful_count|failed_requests|errors|platform_fields
/// failed_request is: api_id_1,correlation_id_1,api_id_2,correlation_id_2|error_1,error_2
/// platform_fields: region_1,region_source_1,region_2,region_source_2
/// </summary>
public string GetLastRequestHeader()
{
var failedRequests = new StringBuilder();
var errors = new StringBuilder();
bool firstFailure = true;
var platformFields = new StringBuilder();
foreach (var ev in _failedEvents)
{
if (!firstFailure)
errors.Append(",");
errors.Append(
// error codes come from the server / broker and can sometimes be full blown sentences,
// with punctuation that is illegal in an HTTP Header
HttpHeaderSantizer.SantizeHeader(ev.ApiErrorCode));
if (!firstFailure)
failedRequests.Append(",");
failedRequests.Append(ev.ApiIdString);
failedRequests.Append(",");
failedRequests.Append(ev.CorrelationId);
if (!firstFailure)
platformFields.Append(",");
ev.TryGetValue(MsalTelemetryBlobEventNames.RegionSource, out string regionSource);
platformFields.Append(ev.RegionDiscovered);
platformFields.Append(",");
platformFields.Append(regionSource);
firstFailure = false;
}
string data =
$"{TelemetryConstants.HttpTelemetrySchemaVersion}|" +
$"{_successfulSilentCallCount}|" +
$"{failedRequests}|" +
$"{errors}|" +
$"{platformFields}";
// TODO: fix this
if (data.Length > 3800)
{
ResetPreviousUnsentData();
return string.Empty;
}
return data;
}
/// <summary>
/// Expected format: 3|api_id,cache_info|platform_config
/// platform_config: region,region_source,is_token_cache_serialized,user_provided_region,validate_use_region,fallback_to_global,is_legacy_cache_enabled
/// </summary>
public string GetCurrentRequestHeader(ApiEvent eventInProgress)
{
if (eventInProgress == null)
{
return string.Empty;
}
eventInProgress.TryGetValue(MsalTelemetryBlobEventNames.ApiIdConstStrKey, out string apiId);
eventInProgress.TryGetValue(MsalTelemetryBlobEventNames.CacheInfoKey, out string cacheInfo);
eventInProgress.TryGetValue(MsalTelemetryBlobEventNames.RegionDiscovered, out string regionDiscovered);
eventInProgress.TryGetValue(MsalTelemetryBlobEventNames.RegionSource, out string regionSource);
eventInProgress.TryGetValue(MsalTelemetryBlobEventNames.IsTokenCacheSerializedKey, out string isTokenCacheSerialized);
eventInProgress.TryGetValue(MsalTelemetryBlobEventNames.UserProvidedRegion, out string userProvidedRegion);
eventInProgress.TryGetValue(MsalTelemetryBlobEventNames.IsValidUserProvidedRegion, out string isValidUserProvidedRegion);
eventInProgress.TryGetValue(MsalTelemetryBlobEventNames.FallbackToGlobal, out string fallbackToGlobal);
eventInProgress.TryGetValue(MsalTelemetryBlobEventNames.IsLegacyCacheEnabledKey, out string isLegacyCacheEnabled);
// Since regional fields will only be logged in case it is opted.
var platformConfig = new StringBuilder();
platformConfig.Append(regionDiscovered + ",");
platformConfig.Append(regionSource + ",");
platformConfig.Append(ConvertFromStringToBitwise(isTokenCacheSerialized) + ",");
platformConfig.Append(userProvidedRegion + ",");
// The value for this will be 1 if the region provided is valid, 0 if invalid and "" in case it could not be validated.
platformConfig.Append((string.IsNullOrEmpty(isValidUserProvidedRegion) ? isValidUserProvidedRegion : ConvertFromStringToBitwise(isValidUserProvidedRegion)) + ",");
platformConfig.Append((string.IsNullOrEmpty(fallbackToGlobal) ? fallbackToGlobal : ConvertFromStringToBitwise(fallbackToGlobal)) + ",");
platformConfig.Append(ConvertFromStringToBitwise(isLegacyCacheEnabled));
return $"{TelemetryConstants.HttpTelemetrySchemaVersion}" +
$"|{apiId},{cacheInfo}" +
$"|{platformConfig}";
}
private string ConvertFromStringToBitwise(string value)
{
if (string.IsNullOrEmpty(value) || value == TelemetryConstants.False)
{
return TelemetryConstants.Zero;
}
return TelemetryConstants.One;
}
}
}