File: Telemetry\TelemetryConstants.cs
Web Access
Project: src\src\Libraries\Microsoft.Extensions.AI.Evaluation.Console\Microsoft.Extensions.AI.Evaluation.Console.csproj (Microsoft.Extensions.AI.Evaluation.Console)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
 
namespace Microsoft.Extensions.AI.Evaluation.Console.Telemetry;
 
internal static class TelemetryConstants
{
    internal const string ConnectionString = "InstrumentationKey=469489a6-628b-4bb9-80db-ec670f70d874";
 
    internal const string EventNamespace = "dotnet/aieval";
 
    internal static class EventNames
    {
        internal const string CleanCacheCommand = "CleanCacheCommand";
        internal const string CleanResultsCommand = "CleanResultsCommand";
        internal const string ReportCommand = "ReportCommand";
 
        internal const string ScenarioRunResult = "ScenarioRunResult";
        internal const string BuiltInMetric = "BuiltInMetric";
        internal const string ModelUsageDetails = "ModelUsageDetails";
    }
 
    internal static class PropertyNames
    {
        // Properties common to all events.
        internal const string DevDeviceId = "DevDeviceId";
        internal const string OSVersion = "OSVersion";
        internal const string OSPlatform = "OSPlatform";
        internal const string KernelVersion = "KernelVersion";
        internal const string RuntimeId = "RuntimeId";
        internal const string ProductVersion = "ProductVersion";
        internal const string IsCIEnvironment = "IsCIEnvironment";
 
        // Properties common to all *Command events.
        internal const string Success = "Success";
        internal const string DurationInMilliseconds = "DurationInMilliseconds";
 
        // Properties for parameters included in corresponding *Command events.
        internal const string StorageType = "StorageType";
        internal const string LastN = "LastN";
        internal const string Format = "Format";
        internal const string OpenReport = "OpenReport";
 
        // Properties included in the ScenarioRunResult event.
        internal const string ScenarioRunResultId = "ScenarioRunResultId";
        internal const string MetricsCount = "MetricsCount";
 
        // Properties included in the BuiltInMetric event.
        internal const string MetricName = "MetricName";
        internal const string InputTokenCount = "InputTokenCount";
        internal const string OutputTokenCount = "OutputTokenCount";
        internal const string IsInterpretedAsFailed = "IsInterpretedAsFailed";
        internal const string ErrorDiagnosticsCount = "ErrorDiagnosticsCount";
        internal const string WarningDiagnosticsCount = "WarningDiagnosticsCount";
        internal const string InformationalDiagnosticsCount = "InformationalDiagnosticsCount";
 
        // Properties included in the ModelUsageDetails event.
        internal const string Model = "Model";
        internal const string ModelProvider = "ModelProvider";
        internal const string IsModelHostWellKnown = "IsModelHostWellKnown";
        internal const string IsModelHostedLocally = "IsModelHostedLocally";
        internal const string CachedTurnCount = "CachedTurnCount";
        internal const string NonCachedTurnCount = "NonCachedTurnCount";
        internal const string CachedInputTokenCount = "CachedInputTokenCount";
        internal const string NonCachedInputTokenCount = "NonCachedInputTokenCount";
        internal const string CachedOutputTokenCount = "CachedOutputTokenCount";
        internal const string NonCachedOutputTokenCount = "NonCachedOutputTokenCount";
    }
 
    internal static class PropertyValues
    {
        internal const string Unknown = "unknown";
 
        internal const string StorageTypeDisk = "disk";
        internal const string StorageTypeAzure = "azure";
 
        // Kusto recongnizes "true" and "false" as boolean literals.
        internal const string True = "true";
        internal const string False = "false";
    }
 
    private const string TelemetryOptOutEnvironmentVariableName = "DOTNET_AIEVAL_TELEMETRY_OPTOUT";
    private const string SkipFirstTimeExperienceEnvironmentVariableName = "DOTNET_AIEVAL_SKIP_FIRST_TIME_EXPERIENCE";
    private const string TelemetryOptOutMessage =
        $"""
        ---------
        Telemetry
        ---------
        The aieval .NET tool collects usage data in order to help us improve your experience. The data is anonymous and doesn't include personal information. You can opt-out of this data collection by setting the {TelemetryOptOutEnvironmentVariableName} environment variable to '1' or 'true' using your favorite shell.
        """;
 
    private static readonly string? _firstUseSentinelFilePath;
    private static readonly bool _firstUseSentinelFileExists;
    private static readonly bool _shouldDisplayTelemetryOptOutMessage;
 
    internal static bool IsTelemetryEnabled { get; }
 
#pragma warning disable CA1810
    // CA1810: Initialize all static fields when declared.
    // We disable this warning because the static fields above must be initialized in a specific order which is not
    // guaranteed when initializing via field initializers.
    static TelemetryConstants()
#pragma warning restore CA1810
    {
        _firstUseSentinelFilePath = GetFirstUseSentinelFilePath();
        _firstUseSentinelFileExists = File.Exists(_firstUseSentinelFilePath);
 
        _shouldDisplayTelemetryOptOutMessage =
            !EnvironmentHelper.GetEnvironmentVariableAsBoolean(SkipFirstTimeExperienceEnvironmentVariableName) &&
            !_firstUseSentinelFileExists;
 
        IsTelemetryEnabled =
            !EnvironmentHelper.GetEnvironmentVariableAsBoolean(TelemetryOptOutEnvironmentVariableName) &&
            !_shouldDisplayTelemetryOptOutMessage;
 
        static string? GetFirstUseSentinelFilePath()
        {
            string? homeDirectoryPath = Environment.GetEnvironmentVariable("DOTNET_CLI_HOME");
            if (string.IsNullOrWhiteSpace(homeDirectoryPath))
            {
                homeDirectoryPath =
                    Environment.GetEnvironmentVariable(
                        RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "USERPROFILE" : "HOME");
            }
 
            string? sentinelFilePath =
                string.IsNullOrWhiteSpace(homeDirectoryPath)
                    ? null
                    : Path.Combine(homeDirectoryPath, ".dotnet", $"{Constants.Version}.aieval.dotnetFirstUseSentinel");
 
            return sentinelFilePath;
        }
    }
 
#pragma warning disable EA0014 // The async method doesn't support cancellation.
    internal static async Task DisplayTelemetryOptOutMessageIfNeededAsync(this ILogger logger)
#pragma warning restore EA0014
    {
        if (_shouldDisplayTelemetryOptOutMessage)
        {
#pragma warning disable CA1303 // Do not pass literals as localized parameters.
            // Use Console.WriteLine directly instead of ILogger to ensure proper formatting.
            System.Console.WriteLine(TelemetryOptOutMessage);
            System.Console.WriteLine();
#pragma warning restore CA1303
        }
 
        if (_firstUseSentinelFilePath is null)
        {
            logger.LogWarning("Could not determine sentinel file path.");
            return;
        }
 
        if (_firstUseSentinelFileExists)
        {
            return;
        }
 
        try
        {
            await File.WriteAllBytesAsync(_firstUseSentinelFilePath, []).ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            logger.LogWarning(ex, "Failed to create sentinel file.");
        }
    }
}