All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.microsoft.aad.msal4j.ServerSideTelemetry Maven / Gradle / Ivy

package com.microsoft.aad.msal4j;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Array;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;

class ServerSideTelemetry {

    private final static Logger log = LoggerFactory.getLogger(ServerSideTelemetry.class);

    private final static String SCHEMA_VERSION = "5";
    private final static String SCHEMA_PIPE_DELIMITER = "|";
    private final static String SCHEMA_COMMA_DELIMITER = ",";
    private final static String CURRENT_REQUEST_HEADER_NAME = "x-client-current-telemetry";
    private final static String LAST_REQUEST_HEADER_NAME = "x-client-last-telemetry";
    private final static int CURRENT_REQUEST_MAX_SIZE = 100;
    private final static int LAST_REQUEST_MAX_SIZE = 350;

    private CurrentRequest currentRequest;
    private AtomicInteger silentSuccessfulCount = new AtomicInteger(0);

    ConcurrentMap previousRequests = new ConcurrentHashMap<>();
    ConcurrentMap previousRequestInProgress = new ConcurrentHashMap<>();

    synchronized Map getServerTelemetryHeaderMap() {
        Map headerMap = new HashMap<>();

        headerMap.put(CURRENT_REQUEST_HEADER_NAME, buildCurrentRequestHeader());
        headerMap.put(LAST_REQUEST_HEADER_NAME, buildLastRequestHeader());

        return headerMap;
    }

    void addFailedRequestTelemetry(String publicApiId, String correlationId, String error) {

        String[] previousRequest = new String[]{publicApiId, error};
        previousRequests.put(
                correlationId,
                previousRequest);
    }

    void incrementSilentSuccessfulCount() {
        silentSuccessfulCount.incrementAndGet();
    }

    synchronized CurrentRequest getCurrentRequest() {
        return currentRequest;
    }

    synchronized void setCurrentRequest(CurrentRequest currentRequest) {
        this.currentRequest = currentRequest;
    }

    private synchronized String buildCurrentRequestHeader() {
        if (currentRequest == null) {
            return StringHelper.EMPTY_STRING;
        }

        String currentRequestHeader = SCHEMA_VERSION + SCHEMA_PIPE_DELIMITER +
                currentRequest.publicApi().getApiId() +
                SCHEMA_COMMA_DELIMITER +
                (currentRequest.cacheInfo() == -1 ? "" : currentRequest.cacheInfo()) +
                SCHEMA_COMMA_DELIMITER +
                currentRequest.regionUsed() +
                SCHEMA_COMMA_DELIMITER +
                currentRequest.regionSource() +
                SCHEMA_COMMA_DELIMITER +
                currentRequest.regionOutcome() +
                SCHEMA_PIPE_DELIMITER;

        if (currentRequestHeader.getBytes(StandardCharsets.UTF_8).length > CURRENT_REQUEST_MAX_SIZE) {
            log.warn("Current request telemetry header greater than 100 bytes");
        }

        return currentRequestHeader;

    }

    private synchronized String buildLastRequestHeader() {

        // LastRequest header schema:
        // schema_version|silent_successful_count|api_id1,correlation_id1|error1|
        StringBuilder lastRequestBuilder = new StringBuilder();

        lastRequestBuilder
                .append(SCHEMA_VERSION)
                .append(SCHEMA_PIPE_DELIMITER)
                .append(silentSuccessfulCount.getAndSet(0));

        // According to spec, lastRequest headers should be smaller than 350 bytes
        int baseLength = lastRequestBuilder.toString().getBytes(StandardCharsets.UTF_8).length;

        if (previousRequests.isEmpty()) {
            // Kusto queries always expect all delimiters so return
            // "schema_version|silent_successful_count|||"
            return lastRequestBuilder
                    .append(SCHEMA_PIPE_DELIMITER)
                    .append(SCHEMA_PIPE_DELIMITER)
                    .append(SCHEMA_PIPE_DELIMITER)
                    .toString();
        }

        StringBuilder middleSegmentBuilder = new StringBuilder(SCHEMA_PIPE_DELIMITER);
        StringBuilder errorSegmentBuilder = new StringBuilder(SCHEMA_PIPE_DELIMITER);

        Iterator it = previousRequests.keySet().iterator();

        // In the case that lastRequestLength > 350, we should still send a string with right delimiters
        String lastRequest = lastRequestBuilder.toString() + SCHEMA_PIPE_DELIMITER + SCHEMA_PIPE_DELIMITER;
        while (it.hasNext()) {

            String correlationId = it.next();
            String[] previousRequest = previousRequests.get(correlationId);
            String apiId = (String) Array.get(previousRequest, 0);
            String error = (String) Array.get(previousRequest, 1);

            middleSegmentBuilder.append(apiId).append(SCHEMA_COMMA_DELIMITER).append(correlationId);
            errorSegmentBuilder.append(error);

            int lastRequestLength = baseLength +
                    middleSegmentBuilder.toString().getBytes(StandardCharsets.UTF_8).length +
                    errorSegmentBuilder.toString().getBytes(StandardCharsets.UTF_8).length;

            // subtract 1 to save 1 byte for the closing delimiter
            if (lastRequestLength < LAST_REQUEST_MAX_SIZE - 1) {
                lastRequest = lastRequestBuilder.toString() +
                        middleSegmentBuilder.toString() +
                        errorSegmentBuilder.toString();
                previousRequestInProgress.put(correlationId, previousRequest);
                it.remove();
            } else {
                break;
            }

            if (it.hasNext()) {
                middleSegmentBuilder.append(SCHEMA_COMMA_DELIMITER);
                errorSegmentBuilder.append(SCHEMA_COMMA_DELIMITER);
            }
        }

        return lastRequest + SCHEMA_PIPE_DELIMITER;

    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy