File: Telemetry\ActivityExtensions.cs
Web Access
Project: ..\..\..\src\Framework\Microsoft.Build.Framework.csproj (Microsoft.Build.Framework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Security.Cryptography;
using System.Text;
using System;
using System.Collections.Generic;
using System.Diagnostics;
 
namespace Microsoft.Build.Framework.Telemetry
{
    /// <summary>
    /// Extension methods for <see cref="Activity"/>. usage in VS OpenTelemetry.
    /// </summary>
    internal static class ActivityExtensions
    {
        /// <summary>
        /// Add tags to the activity from a <see cref="IActivityTelemetryDataHolder"/>.
        /// </summary>
        public static Activity WithTags(this Activity activity, IActivityTelemetryDataHolder? dataHolder)
        {
            if (dataHolder != null)
            {
                activity.WithTags(dataHolder.GetActivityProperties());
            }
            return activity;
        }
 
        /// <summary>
        /// Add tags to the activity from a list of TelemetryItems.
        /// </summary>
        public static Activity WithTags(this Activity activity, IList<TelemetryItem> tags)
        {
            foreach (var tag in tags)
            {
                activity.WithTag(tag);
            }
            return activity;
        }
        /// <summary>
        /// Add a tag to the activity from a <see cref="TelemetryItem"/>.
        /// </summary>
        public static Activity WithTag(this Activity activity, TelemetryItem item)
        {
            object value = item.NeedsHashing ? GetHashed(item.Value) : item.Value;
            activity.SetTag($"{TelemetryConstants.PropertyPrefix}{item.Name}", value);
            return activity;
        }
 
        /// <summary>
        /// Set the start time of the activity.
        /// </summary>
        public static Activity WithStartTime(this Activity activity, DateTime? startTime)
        {
            if (startTime.HasValue)
            {
                activity.SetStartTime(startTime.Value);
            }
            return activity;
        }
 
        /// <summary>
        /// Depending on the platform, hash the value using an available mechanism.
        /// </summary>
        internal static string GetHashed(object value)
        {
            return Sha256Hasher.Hash(value.ToString() ?? "");
        }
 
        // https://github.com/dotnet/sdk/blob/8bd19a2390a6bba4aa80d1ac3b6c5385527cc311/src/Cli/Microsoft.DotNet.Cli.Utils/Sha256Hasher.cs + workaround for netstandard2.0
        private static class Sha256Hasher
        {
            /// <summary>
            /// The hashed mac address needs to be the same hashed value as produced by the other distinct sources given the same input. (e.g. VsCode)
            /// </summary>
            public static string Hash(string text)
            {
                byte[] bytes = Encoding.UTF8.GetBytes(text);
#if NET
                byte[] hash = SHA256.HashData(bytes);
#if NET9_0_OR_GREATER
                return Convert.ToHexStringLower(hash);
#else
                return Convert.ToHexString(hash).ToLowerInvariant();
#endif

#else
                // Create the SHA256 object and compute the hash
                using (var sha256 = SHA256.Create())
                {
                    byte[] hash = sha256.ComputeHash(bytes);
 
                    // Convert the hash bytes to a lowercase hex string (manual loop approach)
                    var sb = new StringBuilder(hash.Length * 2);
                    foreach (byte b in hash)
                    {
                        sb.AppendFormat("{0:x2}", b);
                    }
 
                    return sb.ToString();
                }
#endif
            }
 
            public static string HashWithNormalizedCasing(string text)
            {
                return Hash(text.ToUpperInvariant());
            }
        }
    }
}