| File: Implementation\TelemetryResourceContextFactory.cs | Web Access |
| Project: src\sdk\src\Cli\Microsoft.DotNet.Cli.Telemetry\Microsoft.DotNet.Cli.Telemetry.csproj (Microsoft.DotNet.Cli.Telemetry) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Linq; using System.Reflection; using OpenTelemetry; using OpenTelemetry.Resources; namespace Microsoft.DotNet.Cli.Telemetry.Implementation; /// <summary> /// Builds the <see cref="TelemetryResourceContext"/> (cloud role / role instance / application /// version / SDK version) from an OpenTelemetry <see cref="Resource"/>. Shared by the trace and /// log exporters so both signals stamp identical Application Insights context tags. /// </summary> internal static class TelemetryResourceContextFactory { // OpenTelemetry resource attribute keys (semantic conventions). private const string ServiceNameKey = "service.name"; private const string ServiceNamespaceKey = "service.namespace"; private const string ServiceInstanceIdKey = "service.instance.id"; private const string ServiceVersionKey = "service.version"; public static TelemetryResourceContext FromResource(Resource? resource) { string? serviceName = null; string? serviceNamespace = null; string? roleInstance = null; string? applicationVersion = null; if (resource is not null) { foreach (var attribute in resource.Attributes) { switch (attribute.Key) { case ServiceNameKey: serviceName = attribute.Value?.ToString(); break; case ServiceNamespaceKey: serviceNamespace = attribute.Value?.ToString(); break; case ServiceInstanceIdKey: roleInstance = attribute.Value?.ToString(); break; case ServiceVersionKey: applicationVersion = attribute.Value?.ToString(); break; } } } var roleName = (serviceNamespace, serviceName) switch { (not null, not null) => $"{serviceNamespace}.{serviceName}", (null, not null) => serviceName, _ => serviceNamespace, }; return new TelemetryResourceContext(roleName, roleInstance, applicationVersion, SdkVersion.Value); } /// <summary> /// Replicates the <c>ai.internal.sdkVersion</c> tag produced by the Azure Monitor /// exporter (<c>dotnet<runtime>:otel<otel>:ext<exporter></c>) so that /// dashboards which filter on it continue to work. The Azure Monitor exporter version is /// supplied by the build as assembly metadata (see the <c>AssemblyMetadata</c> item in the /// project file) so this type does not depend on the exporter assembly directly. /// </summary> private static readonly Lazy<string> SdkVersion = new(() => { var dotnet = GetAssemblyVersion(typeof(object)); var otel = GetAssemblyVersion(typeof(Sdk)); var ext = NormalizeVersion(GetExporterVersion()); return $"dotnet{dotnet}:otel{otel}:ext{ext}"; }); // Key of the assembly metadata entry carrying the Azure.Monitor.OpenTelemetry.Exporter // package version. Must match the AssemblyMetadata item defined in the project file. private const string ExporterVersionMetadataKey = "AzureMonitorOpenTelemetryExporterVersion"; private static string? GetExporterVersion() => typeof(TelemetryResourceContextFactory).Assembly .GetCustomAttributes<AssemblyMetadataAttribute>() .FirstOrDefault(a => string.Equals(a.Key, ExporterVersionMetadataKey, StringComparison.Ordinal))? .Value; private static string GetAssemblyVersion(Type type) => NormalizeVersion(type.Assembly .GetCustomAttribute<AssemblyInformationalVersionAttribute>()? .InformationalVersion); private static string NormalizeVersion(string? version) { if (string.IsNullOrEmpty(version)) { return "0.0.0"; } // Strip build metadata / source-revision suffixes, matching the Azure exporter. var separatorIndex = version.IndexOfAny(['+', '@', ' ']); var normalized = separatorIndex >= 0 ? version.Substring(0, separatorIndex) : version; return normalized.Length > 20 ? normalized.Substring(0, 20) : normalized; } }