diff --git a/src/Generators/Microsoft.Gen.Metrics/Emitter.cs b/src/Generators/Microsoft.Gen.Metrics/Emitter.cs index 0e6e7da0091..e384632ca35 100644 --- a/src/Generators/Microsoft.Gen.Metrics/Emitter.cs +++ b/src/Generators/Microsoft.Gen.Metrics/Emitter.cs @@ -94,7 +94,7 @@ private void GenType(MetricType metricType, string nspace) OutLn(); } - GenInstrumentClass(metricMethod); + GenInstrumentClass(metricMethod, metricType.CommonTags); } } @@ -145,7 +145,7 @@ private void GenInstrumentCreateMethods(MetricType metricType, string nspace) } } - private void GenInstrumentClass(MetricMethod metricMethod) + private void GenInstrumentClass(MetricMethod metricMethod, List>? commonTags) { const string CounterObjectName = "counter"; const string HistogramObjectName = "histogram"; @@ -180,7 +180,8 @@ private void GenInstrumentClass(MetricMethod metricMethod) } var tagListInit = metricMethod.TagKeys.Count != 0 || - metricMethod.StrongTypeConfigs.Count != 0; + metricMethod.StrongTypeConfigs.Count != 0 || + (commonTags != null && commonTags.Count != 0); var accessModifier = metricMethod.MetricTypeModifiers.Contains("public") ? "public" @@ -231,7 +232,7 @@ private void GenInstrumentClass(MetricMethod metricMethod) Indent(); OutLn("var tagList = new global::System.Diagnostics.TagList"); OutOpenBrace(); - GenTagList(metricMethod); + GenTagList(metricMethod, commonTags); Unindent(); OutLn("};"); Unindent(); @@ -290,7 +291,7 @@ private void GenInstrumentCreateMethod(MetricMethod metricMethod, string nspace) OutLn(); } - private void GenTagList(MetricMethod metricMethod) + private void GenTagList(MetricMethod metricMethod, List>? commonTags) { if (string.IsNullOrEmpty(metricMethod.StrongTypeObjectName)) { @@ -322,6 +323,14 @@ private void GenTagList(MetricMethod metricMethod) OutLn($"new global::System.Collections.Generic.KeyValuePair(\"{config.TagName}\", {access}),"); } } + + if (commonTags != null) + { + foreach (var tag in commonTags) + { + OutLn($"new global::System.Collections.Generic.KeyValuePair(\"{tag.Key}\", \"{tag.Value}\"),"); + } + } } private void GenTagsParameters(MetricMethod metricMethod) diff --git a/src/Generators/Microsoft.Gen.Metrics/Model/MetricType.cs b/src/Generators/Microsoft.Gen.Metrics/Model/MetricType.cs index e96ef625fff..08886d69f79 100644 --- a/src/Generators/Microsoft.Gen.Metrics/Model/MetricType.cs +++ b/src/Generators/Microsoft.Gen.Metrics/Model/MetricType.cs @@ -14,4 +14,5 @@ internal sealed class MetricType public string Modifiers = string.Empty; public string Keyword = string.Empty; public MetricType? Parent; + public List>? CommonTags; } diff --git a/src/Generators/Microsoft.Gen.Metrics/Parser.cs b/src/Generators/Microsoft.Gen.Metrics/Parser.cs index da05c6a51ec..8903f3bd4e2 100644 --- a/src/Generators/Microsoft.Gen.Metrics/Parser.cs +++ b/src/Generators/Microsoft.Gen.Metrics/Parser.cs @@ -75,6 +75,8 @@ public IReadOnlyList GetMetricClasses(IEnumerable> GetCommonTags(TypeDeclarationSyntax classDeclaration, SymbolHolder symbols, SemanticModel semanticModel, CancellationToken cancellationToken) + { + List> commonTags = new(); + var attributes = classDeclaration.AttributeLists.SelectMany(al => al.Attributes); + + foreach (var attribute in attributes) + { + var attributeTypeInfo = semanticModel.GetTypeInfo(attribute, cancellationToken); + if (attributeTypeInfo.Type == null || !attributeTypeInfo.Type.Equals(symbols.CommonTagAttribute, SymbolEqualityComparer.Default)) + { + continue; + } + + if (attribute.ArgumentList != null) + { + KeyValuePair tag; + string tagKey = string.Empty; + string tagValue = string.Empty; + + foreach (var argument in attribute.ArgumentList.Arguments) + { + var key = argument.NameColon?.Name.ToString(); + var value = argument.Expression.ToString().Trim('"'); + if (key != null && !string.IsNullOrWhiteSpace(key)) + { + if (key == "tagName") + { + tagKey = value; + } + else if (key == "tagValue") + { + tagValue = value; + } + } + } + + if (!string.IsNullOrWhiteSpace(tagKey)) + { + tag = new KeyValuePair(tagKey, tagValue); + } + + commonTags.Add(tag); + } + } + + return commonTags; + } + private static void UpdateMetricKeywordIfRequired(TypeDeclarationSyntax? typeDeclaration, MetricType metricType) { if (typeDeclaration.IsKind(SyntaxKind.RecordStructDeclaration) && diff --git a/src/Generators/Microsoft.Gen.Metrics/SymbolHolder.cs b/src/Generators/Microsoft.Gen.Metrics/SymbolHolder.cs index dec31bcc7b9..2ee13fc5b9a 100644 --- a/src/Generators/Microsoft.Gen.Metrics/SymbolHolder.cs +++ b/src/Generators/Microsoft.Gen.Metrics/SymbolHolder.cs @@ -15,4 +15,5 @@ internal sealed record class SymbolHolder( INamedTypeSymbol? HistogramOfTAttribute, INamedTypeSymbol? GaugeAttribute, INamedTypeSymbol LongTypeSymbol, - INamedTypeSymbol? TagNameAttribute); + INamedTypeSymbol? TagNameAttribute, + INamedTypeSymbol? CommonTagAttribute); diff --git a/src/Generators/Microsoft.Gen.Metrics/SymbolLoader.cs b/src/Generators/Microsoft.Gen.Metrics/SymbolLoader.cs index 4c8bbfc31d8..3a8cd73c302 100644 --- a/src/Generators/Microsoft.Gen.Metrics/SymbolLoader.cs +++ b/src/Generators/Microsoft.Gen.Metrics/SymbolLoader.cs @@ -12,6 +12,7 @@ internal static class SymbolLoader internal const string GaugeAttribute = "Microsoft.Extensions.Diagnostics.Metrics.GaugeAttribute"; internal const string CounterAttribute = "Microsoft.Extensions.Diagnostics.Metrics.CounterAttribute"; internal const string HistogramAttribute = "Microsoft.Extensions.Diagnostics.Metrics.HistogramAttribute"; + internal const string CommonTagAttribute = "Microsoft.Extensions.Diagnostics.Metrics.CommonTagAttribute"; internal const string TagNameAttribute = "Microsoft.Extensions.Diagnostics.Metrics.TagNameAttribute"; internal const string MeterClass = "System.Diagnostics.Metrics.Meter"; @@ -33,6 +34,7 @@ internal static class SymbolLoader var histogramTAttribute = compilation.GetTypeByMetadataName(HistogramTAttribute); var gaugeAttribute = compilation.GetTypeByMetadataName(GaugeAttribute); var tagNameAttribute = compilation.GetTypeByMetadataName(TagNameAttribute); + var commonTagAttribute = compilation.GetTypeByMetadataName(CommonTagAttribute); var longType = compilation.GetSpecialType(SpecialType.System_Int64); return new( @@ -43,6 +45,7 @@ internal static class SymbolLoader histogramTAttribute, gaugeAttribute, longType, - tagNameAttribute); + tagNameAttribute, + commonTagAttribute); } } diff --git a/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Metrics/CommonTagAttribute.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Metrics/CommonTagAttribute.cs new file mode 100644 index 00000000000..20f773b9fb5 --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Metrics/CommonTagAttribute.cs @@ -0,0 +1,55 @@ +// 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.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Shared.Diagnostics; + +namespace Microsoft.Extensions.Telemetry.Metrics; + +/// +/// Provides a way to add common tags to the all the metrics in this class. +/// +/// +/// +/// [CommonTag("Category", "Http")] +/// [CommonTag("MetricVersion", "v1")] +/// static partial class Metric +/// { +/// [Counter("RequestName", "RequestStatusCode")] +/// static partial RequestCounter CreateRequestCounter(Meter meter); +/// } +/// +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] +[Conditional("CODE_GENERATION_ATTRIBUTES")] +[Experimental("EXPERIMENTAL_COMMON_TAG_ATTRIBUTE")] +public sealed class CommonTagAttribute : Attribute +{ + /// + /// Initializes a new instance of the class. + /// + /// Tag name. + /// Tag value. + public CommonTagAttribute(string tagName, string tagValue) + { + if (string.IsNullOrEmpty(tagName)) + { + Throw.ArgumentException(nameof(tagName), "tagName name cannot be null or empty."); + } + + TagName = tagName; + TagValue = tagValue; + } + + /// + /// Gets the metric's tag name. + /// + public string TagName { get; } + + /// + /// Gets the metric's tag value. + /// + public string TagValue { get; } +}