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

com.microsoft.azure.toolkit.lib.common.telemetry.AzureTelemeter Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License. See License.txt in the project root for license information.
 */

package com.microsoft.azure.toolkit.lib.common.telemetry;

import com.google.common.collect.ImmutableMap;
import com.microsoft.azure.toolkit.lib.common.bundle.AzureString;
import com.microsoft.azure.toolkit.lib.common.operation.MethodOperation;
import com.microsoft.azure.toolkit.lib.common.operation.Operation;
import com.microsoft.azure.toolkit.lib.common.operation.OperationBundle;
import com.microsoft.azure.toolkit.lib.common.operation.OperationContext;
import com.microsoft.azure.toolkit.lib.common.operation.SimpleOperation;
import com.microsoft.azure.toolkit.lib.common.telemetry.AzureTelemetry.Properties;
import com.microsoft.azure.toolkit.lib.common.telemetry.AzureTelemetry.Property;
import lombok.Getter;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.tuple.Triple;
import org.jetbrains.annotations.PropertyKey;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.reflect.Parameter;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

public class AzureTelemeter {
    public static final String INFO_BUNDLE = "bundles.com.microsoft.azure.toolkit.info";

    public static final String SERVICE_NAME = "serviceName";
    public static final String OPERATION_NAME = "operationName";
    public static final String OP_ID = "op_id";
    public static final String OP_NAME = "op_name";
    public static final String OP_TYPE = "op_type";
    public static final String OP_PARENT_ID = "op_parentId";

    public static final String INFO_NAME = "info.name";
    public static final String INFO_SERVICE = "info.service";
    public static final String INFO_DETAILS = "info.details";
    public static final String ERROR_CODE = "error.error_code";
    public static final String ERROR_MSG = "error.error_msg";
    public static final String ERROR_ROOT_MSG = "error.root_error_message";
    public static final String ERROR_TYPE = "error.error_type";
    public static final String ERROR_CLASSNAME = "error.error_class_name";
    public static final String ERROR_ROOT_CLASSNAME = "error.root_error_class_name";
    public static final String ERROR_STACKTRACE = "error.error_stack_trace";
    @Nullable
    private static AzureTelemetryClient client = null;

    public static synchronized AzureTelemetryClient getClient() {
        if (client == null) {
            client = new AzureTelemetryClient();
        }
        return client;
    }

    public static void addCommonProperties(@Nonnull Map commonProperties) {
        getClient().addDefaultProperties(commonProperties);
    }

    public static void addCommonProperty(@Nonnull String key, @Nonnull String value) {
        getClient().addDefaultProperty(key, value);
    }

    public static void setEventNamePrefix(@Nonnull String prefix) {
        getClient().setEventNamePrefix(prefix);
    }

    public static void afterCreate(@Nonnull final Operation op) {
        op.getContext().setTelemetryProperty(AzureTelemetry.OP_CREATE_AT, Instant.now().toString());
    }

    public static void beforeEnter(@Nonnull final Operation op) {
        op.getContext().setTelemetryProperty(AzureTelemetry.OP_ENTER_AT, Instant.now().toString());
    }

    public static void afterExit(@Nonnull final Operation op) {
        op.getContext().setTelemetryProperty(AzureTelemetry.OP_EXIT_AT, Instant.now().toString());
        AzureTelemeter.log(AzureTelemetry.Type.OP_END, serialize(op));
    }

    public static void onError(@Nonnull final Operation op, Throwable error) {
        op.getContext().setTelemetryProperty(AzureTelemetry.OP_EXIT_AT, Instant.now().toString());
        AzureTelemeter.log(AzureTelemetry.Type.ERROR, serialize(op), error);
    }

    public static void info(@Nonnull @PropertyKey(resourceBundle = INFO_BUNDLE) final String key) {
        AzureTelemeter.log(AzureTelemetry.Type.INFO, ImmutableMap.of(INFO_NAME, StringUtils.substringAfter(key, "."), INFO_SERVICE, StringUtils.substringBefore(key, ".")));
    }

    public static void info(@Nonnull @PropertyKey(resourceBundle = INFO_BUNDLE) final String key, final String details) {
        AzureTelemeter.log(AzureTelemetry.Type.INFO, ImmutableMap.of(INFO_NAME, StringUtils.substringAfter(key, "."), INFO_SERVICE, StringUtils.substringBefore(key, "."), INFO_DETAILS, details));
    }

    public static void info(@Nonnull @PropertyKey(resourceBundle = INFO_BUNDLE) final String key, Map details) {
        final HashMap map = new HashMap<>(details);
        map.put(INFO_NAME, StringUtils.substringAfter(key, "."));
        map.put(INFO_SERVICE, StringUtils.substringBefore(key, "."));
        AzureTelemeter.log(AzureTelemetry.Type.INFO, map);
    }

    public static void log(final AzureTelemetry.Type type, final String opId) {
        AzureTelemeter.log(type, serialize(new SimpleOperation(OperationBundle.description(opId), () -> null, null)));
    }

    public static void log(final AzureTelemetry.Type type, final AzureString op) {
        AzureTelemeter.log(type, serialize(new SimpleOperation(op, () -> null, null)));
    }

    public static void log(final AzureTelemetry.Type type, final String opId, final Throwable e) {
        AzureTelemeter.log(type, serialize(new SimpleOperation(OperationBundle.description(opId), () -> null, null)), e);
    }

    public static void log(final AzureTelemetry.Type type, final AzureString op, final Throwable e) {
        AzureTelemeter.log(type, serialize(new SimpleOperation(op, () -> null, null)), e);
    }

    public static void log(final AzureTelemetry.Type type, final Map properties, final Throwable e) {
        if (Objects.nonNull(e)) {
            properties.putAll(serialize(e));
        }
        AzureTelemeter.log(type, properties);
    }

    public static void log(final AzureTelemetry.Type type, final Map properties) {
        if (!StringUtils.equals(properties.get(OP_NAME), Operation.UNKNOWN_NAME)) {
            final String eventName = Optional.ofNullable(getClient().getEventNamePrefix()).orElse("AzurePlugin") + "/" + type.getName();
            getClient().trackEvent(eventName, properties, null);
        }
    }

    @Nonnull
    private static Map serialize(@Nonnull final Operation op) {
        final OperationContext context = op.getContext();
        final Map actionProperties = getActionProperties(op);
        final Optional parent = Optional.ofNullable(op.getEffectiveParent());
        final Map properties = new HashMap<>();
        final String name = op.getId().replaceAll("\\(.+\\)", "(***)"); // e.g. `internal/$appservice.list_file.dir`
        properties.put(OP_ID, op.getExecutionId());
        properties.put(OP_PARENT_ID, parent.map(Operation::getExecutionId).orElse("/"));
        properties.put(OP_NAME, name);
        properties.put(OP_TYPE, op.getType());
        properties.put(SERVICE_NAME, op.getServiceName());
        properties.put(OPERATION_NAME, op.getOperationName()); // "list_file"
        properties.putAll(actionProperties);
        if (op instanceof MethodOperation) {
            properties.putAll(getParameterProperties((MethodOperation) op));
        }
        properties.putAll(context.getTelemetryProperties());
        return properties;
    }

    private static Map getParameterProperties(MethodOperation ref) {
        final HashMap properties = new HashMap<>();
        final List> args = ref.getInvocation().getArgs();
        for (final Triple arg : args) {
            final Parameter param = arg.getMiddle();
            final Object value = arg.getRight();
            Optional.ofNullable(param.getAnnotation(Property.class))
                .map(Property::value)
                .map(n -> Property.PARAM_NAME.equals(n) ? param.getName() : n)
                .ifPresent((name) -> properties.put(name, Optional.ofNullable(value).map(Object::toString).orElse("")));
            Optional.ofNullable(param.getAnnotation(Properties.class))
                .map(Properties::value)
                .map(AzureTelemeter::instantiate)
                .map(converter -> converter.convert(value))
                .ifPresent(properties::putAll);
        }
        return properties;
    }

    @Nonnull
    private static Map getActionProperties(@Nonnull Operation operation) {
        return Optional.ofNullable(operation.getActionParent())
            .map(Operation::getContext)
            .map(OperationContext::getTelemetryProperties)
            .orElse(new HashMap<>());
    }

    @SneakyThrows
    private static  U instantiate(Class clazz) {
        return clazz.newInstance();
    }

    @Nonnull
    private static HashMap serialize(@Nonnull Throwable e) {
        final HashMap properties = new HashMap<>();
        final ErrorType type = ErrorType.userError; // TODO: (@wangmi & @Hanxiao.Liu)decide error type based on the type of ex.
        properties.put(ERROR_CLASSNAME, e.getClass().getName());
        properties.put(ERROR_TYPE, type.name());
        properties.put(ERROR_MSG, e.getMessage());
        Optional.ofNullable(ExceptionUtils.getRootCause(e)).ifPresent(root -> {
            properties.put(ERROR_ROOT_MSG, root.getMessage());
            properties.put(ERROR_ROOT_CLASSNAME, root.getClass().getName());
        });
        properties.put(ERROR_STACKTRACE, ExceptionUtils.getStackTrace(e));
        return properties;
    }

    private enum ErrorType {
        userError,
        systemError,
        serviceError,
        toolError,
        unclassifiedError
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy