Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions src/Generators/Microsoft.Gen.Metrics/Emitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ private void GenType(MetricType metricType, string nspace)
OutLn();
}

GenInstrumentClass(metricMethod);
GenInstrumentClass(metricMethod, metricType.CommonTags);
}
}

Expand Down Expand Up @@ -145,7 +145,7 @@ private void GenInstrumentCreateMethods(MetricType metricType, string nspace)
}
}

private void GenInstrumentClass(MetricMethod metricMethod)
private void GenInstrumentClass(MetricMethod metricMethod, List<KeyValuePair<string, string>>? commonTags)
{
const string CounterObjectName = "counter";
const string HistogramObjectName = "histogram";
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -290,7 +291,7 @@ private void GenInstrumentCreateMethod(MetricMethod metricMethod, string nspace)
OutLn();
}

private void GenTagList(MetricMethod metricMethod)
private void GenTagList(MetricMethod metricMethod, List<KeyValuePair<string, string>>? commonTags)
{
if (string.IsNullOrEmpty(metricMethod.StrongTypeObjectName))
{
Expand Down Expand Up @@ -322,6 +323,14 @@ private void GenTagList(MetricMethod metricMethod)
OutLn($"new global::System.Collections.Generic.KeyValuePair<string, object?>(\"{config.TagName}\", {access}),");
}
}

if (commonTags != null)
{
foreach (var tag in commonTags)
{
OutLn($"new global::System.Collections.Generic.KeyValuePair<string, object?>(\"{tag.Key}\", \"{tag.Value}\"),");
}
}
}

private void GenTagsParameters(MetricMethod metricMethod)
Expand Down
1 change: 1 addition & 0 deletions src/Generators/Microsoft.Gen.Metrics/Model/MetricType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ internal sealed class MetricType
public string Modifiers = string.Empty;
public string Keyword = string.Empty;
public MetricType? Parent;
public List<KeyValuePair<string, string>>? CommonTags;
}
52 changes: 52 additions & 0 deletions src/Generators/Microsoft.Gen.Metrics/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ public IReadOnlyList<MetricType> GetMetricClasses(IEnumerable<TypeDeclarationSyn

MetricType? metricType = null;
string nspace = string.Empty;
semanticModel ??= _compilation.GetSemanticModel(typeDeclaration.SyntaxTree);
var commonTags = GetCommonTags(typeDeclaration, symbols, semanticModel, _cancellationToken);

metricNames.Clear();

Expand Down Expand Up @@ -135,6 +137,7 @@ potentialNamespaceParent is not NamespaceDeclarationSyntax &&
{
Namespace = nspace,
Name = typeDeclaration.Identifier.ToString() + typeDeclaration.TypeParameterList,
CommonTags = commonTags,
Constraints = typeDeclaration.ConstraintClauses.ToString(),
Keyword = typeDeclaration.Keyword.ValueText,
Parent = null,
Expand All @@ -157,6 +160,7 @@ SyntaxKind.StructDeclaration or
{
Namespace = nspace,
Name = parentMetricClass.Identifier.ToString() + parentMetricClass.TypeParameterList,
CommonTags = commonTags,
Constraints = parentMetricClass.ConstraintClauses.ToString(),
Keyword = parentMetricClass.Keyword.ValueText,
Modifiers = parentMetricClass.Modifiers.ToString(),
Expand Down Expand Up @@ -187,6 +191,54 @@ SyntaxKind.StructDeclaration or
return results;
}

private static List<KeyValuePair<string, string>> GetCommonTags(TypeDeclarationSyntax classDeclaration, SymbolHolder symbols, SemanticModel semanticModel, CancellationToken cancellationToken)
{
List<KeyValuePair<string, string>> 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<string, string> 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<string, string>(tagKey, tagValue);
}

commonTags.Add(tag);
}
}

return commonTags;
}

private static void UpdateMetricKeywordIfRequired(TypeDeclarationSyntax? typeDeclaration, MetricType metricType)
{
if (typeDeclaration.IsKind(SyntaxKind.RecordStructDeclaration) &&
Expand Down
3 changes: 2 additions & 1 deletion src/Generators/Microsoft.Gen.Metrics/SymbolHolder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ internal sealed record class SymbolHolder(
INamedTypeSymbol? HistogramOfTAttribute,
INamedTypeSymbol? GaugeAttribute,
INamedTypeSymbol LongTypeSymbol,
INamedTypeSymbol? TagNameAttribute);
INamedTypeSymbol? TagNameAttribute,
INamedTypeSymbol? CommonTagAttribute);
5 changes: 4 additions & 1 deletion src/Generators/Microsoft.Gen.Metrics/SymbolLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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(
Expand All @@ -43,6 +45,7 @@ internal static class SymbolLoader
histogramTAttribute,
gaugeAttribute,
longType,
tagNameAttribute);
tagNameAttribute,
commonTagAttribute);
}
}
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Provides a way to add common tags to the all the metrics in this class.
/// </summary>
/// <example>
/// <code language="csharp">
/// [CommonTag("Category", "Http")]
/// [CommonTag("MetricVersion", "v1")]
/// static partial class Metric
/// {
/// [Counter("RequestName", "RequestStatusCode")]
/// static partial RequestCounter CreateRequestCounter(Meter meter);
/// }
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
[Conditional("CODE_GENERATION_ATTRIBUTES")]
[Experimental("EXPERIMENTAL_COMMON_TAG_ATTRIBUTE")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should use the well-defined experiment IDs

Suggested change
[Experimental("EXPERIMENTAL_COMMON_TAG_ATTRIBUTE")]
[Experimental(diagnosticId: DiagnosticIds.Experiments.Telemetry, UrlFormat = DiagnosticIds.UrlFormat)]

public sealed class CommonTagAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="CommonTagAttribute"/> class.
/// </summary>
/// <param name="tagName">Tag name.</param>
/// <param name="tagValue">Tag value.</param>
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;
}

/// <summary>
/// Gets the metric's tag name.
/// </summary>
public string TagName { get; }

/// <summary>
/// Gets the metric's tag value.
/// </summary>
public string TagValue { get; }
}
Loading