File: Resolver\DnsResolver.Telemetry.cs
Web Access
Project: src\src\Microsoft.Extensions.ServiceDiscovery.Dns\Microsoft.Extensions.ServiceDiscovery.Dns.csproj (Microsoft.Extensions.ServiceDiscovery.Dns)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Diagnostics.Metrics;
 
namespace Microsoft.Extensions.ServiceDiscovery.Dns.Resolver;
 
internal partial class DnsResolver
{
    internal static class Telemetry
    {
        private static readonly Meter s_meter = new Meter("Microsoft.Extensions.ServiceDiscovery.Dns.Resolver");
        private static readonly Histogram<double> s_queryDuration = s_meter.CreateHistogram<double>("query.duration", "ms", "DNS query duration");
 
        private static bool IsEnabled() => s_queryDuration.Enabled;
 
        public static NameResolutionActivity StartNameResolution(string hostName, QueryType queryType, long startingTimestamp)
        {
            if (IsEnabled())
            {
                return new NameResolutionActivity(hostName, queryType, startingTimestamp);
            }
 
            return default;
        }
 
        public static void StopNameResolution(string hostName, QueryType queryType, in NameResolutionActivity activity, object? answers, SendQueryError error, long endingTimestamp)
        {
            activity.Stop(answers, error, endingTimestamp, out TimeSpan duration);
 
            if (!IsEnabled())
            {
                return;
            }
 
            var hostNameTag = KeyValuePair.Create("dns.question.name", (object?)hostName);
            var queryTypeTag = KeyValuePair.Create("dns.question.type", (object?)queryType);
 
            if (answers is not null)
            {
                s_queryDuration.Record(duration.TotalSeconds, hostNameTag, queryTypeTag);
            }
            else
            {
                var errorTypeTag = KeyValuePair.Create("error.type", (object?)error.ToString());
                s_queryDuration.Record(duration.TotalSeconds, hostNameTag, queryTypeTag, errorTypeTag);
            }
        }
    }
 
    internal readonly struct NameResolutionActivity
    {
        private const string ActivitySourceName = "Microsoft.Extensions.ServiceDiscovery.Dns.Resolver";
        private const string ActivityName = ActivitySourceName + ".Resolve";
        private static readonly ActivitySource s_activitySource = new ActivitySource(ActivitySourceName);
 
        private readonly long _startingTimestamp;
        private readonly Activity? _activity;  // null if activity is not started
 
        public NameResolutionActivity(string hostName, QueryType queryType, long startingTimestamp)
        {
            _startingTimestamp = startingTimestamp;
            _activity = s_activitySource.StartActivity(ActivityName, ActivityKind.Client);
            if (_activity is not null)
            {
                _activity.DisplayName = $"Resolving {hostName}";
                if (_activity.IsAllDataRequested)
                {
                    _activity.SetTag("dns.question.name", hostName);
                    _activity.SetTag("dns.question.type", queryType.ToString());
                }
            }
        }
 
        public void Stop(object? answers, SendQueryError error, long endingTimestamp, out TimeSpan duration)
        {
            duration = Stopwatch.GetElapsedTime(_startingTimestamp, endingTimestamp);
 
            if (_activity is null)
            {
                return;
            }
 
            if (_activity.IsAllDataRequested)
            {
                if (answers is not null)
                {
                    static string[] ToStringHelper<T>(T[] array) => array.Select(a => a!.ToString()!).ToArray();
 
                    string[]? answersArray = answers switch
                    {
                        ServiceResult[] serviceResults => ToStringHelper(serviceResults),
                        AddressResult[] addressResults => ToStringHelper(addressResults),
                        _ => null
                    };
 
                    Debug.Assert(answersArray is not null);
                    _activity.SetTag("dns.answers", answersArray);
                }
                else
                {
                    _activity.SetTag("error.type", error.ToString());
                }
            }
 
            if (answers is null)
            {
                _activity.SetStatus(ActivityStatusCode.Error);
            }
 
            _activity.Stop();
        }
    }
}