Skip to content

Commit 32dee12

Browse files
authored
Merge pull request #135 from AzureAD/sagonzal/serverTelemetryV2
Add server telemetry with v2 schema
2 parents 30ee574 + 632051a commit 32dee12

13 files changed

+364
-7
lines changed

src/main/java/com/microsoft/aad/msal4j/AcquireTokenSilentSupplier.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ AuthenticationResult execute() throws Exception {
5757
if(res == null || StringHelper.isBlank(res.accessToken())){
5858
throw new MsalClientException(AuthenticationErrorMessage.NO_TOKEN_IN_CACHE, AuthenticationErrorCode.CACHE_MISS);
5959
}
60+
61+
clientApplication.getServiceBundle().getServerSideTelemetry().incrementSilentSuccessfulCount();
6062
return res;
6163
}
6264
}

src/main/java/com/microsoft/aad/msal4j/AuthenticationResultSupplier.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,23 @@ public IAuthenticationResult get() {
6868
}
6969
} catch(Exception ex) {
7070

71+
String error;
7172
if (ex instanceof MsalServiceException) {
72-
apiEvent.setApiErrorCode(((MsalServiceException) ex).errorCode());
73+
error = ((MsalServiceException) ex).errorCode();
74+
apiEvent.setApiErrorCode(error);
75+
} else {
76+
if(ex.getCause() != null){
77+
error = ex.getCause().toString();
78+
} else {
79+
error = StringHelper.EMPTY_STRING;
80+
}
7381
}
7482

83+
clientApplication.getServiceBundle().getServerSideTelemetry().addFailedRequestTelemetry(
84+
String.valueOf(msalRequest.requestContext().publicApi().getApiId()),
85+
msalRequest.requestContext().correlationId(),
86+
error);
87+
7588
clientApplication.log.error(
7689
LogHelper.createMessage(
7790
"Execution of " + this.getClass() + " failed.",
@@ -83,8 +96,7 @@ public IAuthenticationResult get() {
8396
return result;
8497
}
8598

86-
void logResult(AuthenticationResult result, HttpHeaders headers)
87-
{
99+
private void logResult(AuthenticationResult result, HttpHeaders headers) {
88100
if (!StringHelper.isBlank(result.accessToken())) {
89101

90102
String accessTokenHash = this.computeSha256Hash(result

src/main/java/com/microsoft/aad/msal4j/ClientApplicationBase.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ abstract static class Builder<T extends Builder<T>> {
251251
private String authority = DEFAULT_AUTHORITY;
252252
private Authority authenticationAuthority = createDefaultAADAuthority();
253253
private boolean validateAuthority = true;
254-
private String correlationId = UUID.randomUUID().toString();
254+
private String correlationId;
255255
private boolean logPii = false;
256256
private ExecutorService executorService;
257257
private Proxy proxy;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.microsoft.aad.msal4j;
5+
6+
import lombok.Getter;
7+
import lombok.Setter;
8+
import lombok.experimental.Accessors;
9+
10+
@Getter
11+
@Accessors(fluent = true)
12+
class CurrentRequest {
13+
14+
private final PublicApi publicApi;
15+
16+
@Setter
17+
private boolean forceRefresh = false;
18+
19+
CurrentRequest(PublicApi publicApi){
20+
this.publicApi = publicApi;
21+
}
22+
}

src/main/java/com/microsoft/aad/msal4j/MsalRequest.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,25 @@
1212
@Getter(AccessLevel.PACKAGE)
1313
@AllArgsConstructor
1414
abstract class MsalRequest {
15-
private final ClientApplicationBase application;
1615

1716
AbstractMsalAuthorizationGrant msalAuthorizationGrant;
1817

18+
private final ClientApplicationBase application;
19+
1920
private final RequestContext requestContext;
2021

2122
@Getter(value = AccessLevel.PACKAGE, lazy = true)
2223
private final HttpHeaders headers = new HttpHeaders(requestContext);
23-
}
2424

25+
MsalRequest(ClientApplicationBase clientApplicationBase,
26+
AbstractMsalAuthorizationGrant abstractMsalAuthorizationGrant,
27+
RequestContext requestContext){
28+
29+
this.application = clientApplicationBase;
30+
this.msalAuthorizationGrant = abstractMsalAuthorizationGrant;
31+
this.requestContext = requestContext;
2532

33+
CurrentRequest currentRequest = new CurrentRequest(requestContext.publicApi());
34+
application.getServiceBundle().getServerSideTelemetry().setCurrentRequest(currentRequest);
35+
}
36+
}

src/main/java/com/microsoft/aad/msal4j/OAuthHttpRequest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ private Map<String, String> configureHttpHeaders(){
6262
if (this.getAuthorization() != null) {
6363
httpHeaders.put("Authorization", this.getAuthorization());
6464
}
65+
66+
Map<String, String> telemetryHeaders =
67+
serviceBundle.getServerSideTelemetry().getServerTelemetryHeaderMap();
68+
httpHeaders.putAll(telemetryHeaders);
69+
6570
return httpHeaders;
6671
}
6772

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package com.microsoft.aad.msal4j;
2+
3+
import java.lang.reflect.Array;
4+
import java.util.HashMap;
5+
import java.util.Iterator;
6+
import java.util.Map;
7+
import java.util.concurrent.ConcurrentHashMap;
8+
import java.util.concurrent.ConcurrentMap;
9+
import java.util.concurrent.atomic.AtomicInteger;
10+
11+
class ServerSideTelemetry {
12+
13+
private final static String SCHEMA_VERSION = "2";
14+
private final static String SCHEMA_PIPE_DELIMITER = "|";
15+
private final static String SCHEMA_COMMA_DELIMITER = ",";
16+
private final static String CURRENT_REQUEST_HEADER_NAME = "x-client-current-telemetry";
17+
private final static String LAST_REQUEST_HEADER_NAME = "x-client-last-telemetry";
18+
19+
private CurrentRequest currentRequest;
20+
private AtomicInteger silentSuccessfulCount = new AtomicInteger(0);
21+
22+
ConcurrentMap<String, String[]> previousRequests = new ConcurrentHashMap<>();
23+
ConcurrentMap<String, String[]> previousRequestInProgress = new ConcurrentHashMap<>();
24+
25+
synchronized Map<String, String> getServerTelemetryHeaderMap(){
26+
Map<String, String> headerMap = new HashMap<>();
27+
28+
headerMap.put(CURRENT_REQUEST_HEADER_NAME, buildCurrentRequestHeader());
29+
headerMap.put(LAST_REQUEST_HEADER_NAME, buildLastRequestHeader());
30+
31+
return headerMap;
32+
}
33+
34+
void addFailedRequestTelemetry(String publicApiId, String correlationId, String error){
35+
36+
String[] previousRequest = new String[]{publicApiId, error};
37+
previousRequests.put(
38+
correlationId,
39+
previousRequest);
40+
}
41+
42+
void incrementSilentSuccessfulCount(){
43+
silentSuccessfulCount.incrementAndGet();
44+
}
45+
46+
synchronized CurrentRequest getCurrentRequest() {
47+
return currentRequest;
48+
}
49+
50+
synchronized void setCurrentRequest(CurrentRequest currentRequest) {
51+
this.currentRequest = currentRequest;
52+
}
53+
54+
private synchronized String buildCurrentRequestHeader(){
55+
if(currentRequest == null){
56+
return StringHelper.EMPTY_STRING;
57+
}
58+
59+
return SCHEMA_VERSION +
60+
SCHEMA_PIPE_DELIMITER +
61+
currentRequest.publicApi().getApiId() +
62+
SCHEMA_COMMA_DELIMITER +
63+
currentRequest.forceRefresh() +
64+
SCHEMA_PIPE_DELIMITER;
65+
}
66+
67+
private synchronized String buildLastRequestHeader() {
68+
69+
// LastRequest header schema:
70+
// schema_version|silent_successful_count|api_id1,correlation_id1|error1|
71+
StringBuilder lastRequestBuilder = new StringBuilder();
72+
73+
lastRequestBuilder
74+
.append(SCHEMA_VERSION)
75+
.append(SCHEMA_PIPE_DELIMITER)
76+
.append(silentSuccessfulCount.getAndSet(0));
77+
78+
if (previousRequests.isEmpty()) {
79+
// Kusto queries always expect all delimiters so return
80+
// "schema_version|silent_successful_count|||"
81+
return lastRequestBuilder
82+
.append(SCHEMA_PIPE_DELIMITER)
83+
.append(SCHEMA_PIPE_DELIMITER)
84+
.append(SCHEMA_PIPE_DELIMITER)
85+
.toString();
86+
}
87+
88+
StringBuilder middleSegmentBuilder = new StringBuilder(SCHEMA_PIPE_DELIMITER);
89+
StringBuilder errorSegmentBuilder = new StringBuilder(SCHEMA_PIPE_DELIMITER);
90+
91+
Iterator<String> it = previousRequests.keySet().iterator();
92+
93+
// Total header size should be less than 8kb. At max, we will use 4kb for telemetry.
94+
while (it.hasNext()
95+
&& (middleSegmentBuilder.length() + errorSegmentBuilder.length()) < 3800) {
96+
String correlationId = it.next();
97+
String[] previousRequest = previousRequests.get(correlationId);
98+
String apiId = (String)Array.get(previousRequest, 0);
99+
String error = (String)Array.get(previousRequest, 1);
100+
101+
middleSegmentBuilder.append(apiId).append(SCHEMA_COMMA_DELIMITER).append(correlationId);
102+
errorSegmentBuilder.append(error);
103+
104+
if(it.hasNext()){
105+
middleSegmentBuilder.append(SCHEMA_COMMA_DELIMITER);
106+
errorSegmentBuilder.append(SCHEMA_COMMA_DELIMITER);
107+
}
108+
109+
previousRequestInProgress.put(correlationId, previousRequest);
110+
it.remove();
111+
}
112+
113+
errorSegmentBuilder.append(SCHEMA_PIPE_DELIMITER);
114+
115+
return lastRequestBuilder.append(middleSegmentBuilder).append(errorSegmentBuilder).toString();
116+
}
117+
}

src/main/java/com/microsoft/aad/msal4j/ServiceBundle.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@ class ServiceBundle {
1010
private ExecutorService executorService;
1111
private TelemetryManager telemetryManager;
1212
private IHttpClient httpClient;
13+
private ServerSideTelemetry serverSideTelemetry;
1314

1415
ServiceBundle(ExecutorService executorService, IHttpClient httpClient,
1516
TelemetryManager telemetryManager){
1617
this.executorService = executorService;
1718
this.telemetryManager = telemetryManager;
1819
this.httpClient = httpClient;
20+
21+
serverSideTelemetry = new ServerSideTelemetry();
1922
}
2023

2124
ExecutorService getExecutorService() {
@@ -29,4 +32,8 @@ TelemetryManager getTelemetryManager(){
2932
IHttpClient getHttpClient(){
3033
return httpClient;
3134
}
35+
36+
ServerSideTelemetry getServerSideTelemetry(){
37+
return serverSideTelemetry;
38+
}
3239
}

src/main/java/com/microsoft/aad/msal4j/SilentRequest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,8 @@ class SilentRequest extends MsalRequest {
2727
this.requestAuthority = StringHelper.isBlank(parameters.authorityUrl()) ?
2828
application.authenticationAuthority :
2929
Authority.createAuthority(new URL(parameters.authorityUrl()));
30+
31+
application.getServiceBundle().getServerSideTelemetry().getCurrentRequest().forceRefresh(
32+
parameters.forceRefresh());
3033
}
3134
}

src/main/java/com/microsoft/aad/msal4j/StringHelper.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
final class StringHelper {
1212

13+
static String EMPTY_STRING = "";
14+
1315
static boolean isBlank(final String str) {
1416
return str == null || str.trim().length() == 0;
1517
}

0 commit comments

Comments
 (0)