Skip to content
Purview Dev logo

Metrics

Stable Telemetry SourceGenerator Reviewed 2026-10-28 purview-dev/telemetry-sourcegenerator Star on GitHub 36 activity c-sharp distributed-tracing dotnet events high-performance-logging instrumentation logging melt metrics nuget observability open-telemetry open-telemetry-csharp otel roslyn source-generator spans telemetry tracing

All metric-related attributes live in the Purview.Telemetry namespace. To signal an interface for meter generation, decorate it with the [Meter] attribute.

When creating the meter types, the IMeterFactory is used when available (it is not available on .NET Framework 4.8, where a new Meter(...) is created instead).

The meter name is resolved in this order:

  1. MeterAttribute.Name (interface)
  2. MeterGenerationAttribute.MeterName (assembly)
  3. The assembly name
[Meter("InventoryService")]
interface IInventoryMetrics { }

An instrument name is built from up to four dot-separated parts, outermost first:

  1. The meter name - only with [assembly: MeterGeneration(MeterNameGenerationType = MeterNameGenerationType.OpenTelemetry)]. The default, MeterNameGenerationType.DotNet, leaves it out: the meter already identifies its instruments.
  2. MeterGenerationAttribute.InstrumentPrefix (assembly), unless MeterAttribute.IncludeAssemblyInstrumentPrefix is false.
  3. The interface prefix: MeterAttribute.InstrumentPrefix when set, otherwise derived from the interface name by removing the leading I and a trailing Telemetry or Logs (IProductTelemetry becomes product).
  4. The instrument’s Name, or the method name.

With the default NamingConvention.OpenTelemetry, word boundaries in every part become underscores (RecordCount becomes record_count, MyApp.Products becomes my_app.products); NamingConvention.Legacy only lowercases them. LowercaseInstrumentName = false keeps every part as written.

[Meter("MyApp.Products")]
interface IProductTelemetry
{
[Counter]
void RecordCount([InstrumentMeasurement] int count);
}
ConfigurationInstrument name
Defaultsproduct.record_count
MeterNameGenerationType.OpenTelemetrymy_app.products.product.record_count
NamingConvention.Legacyproduct.recordcount

These three names are pinned by the Generate_GivenTheDocumentedExample* tests in TelemetrySourceGeneratorMetricsTests.MeterNaming.cs.

MeterNameGenerationType.OpenTelemetry also lowercases a meter name that falls back to the assembly name.

During initialisation you can implement a partial method to provide additional tags to any meters created by the IMeterFactory:

partial void PopulateMeterTags(System.Collections.Generic.Dictionary<string, object?> meterTags)
{
meterTags["environment"] = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
}

Any tags added to meterTags are included on every meter created.

Each instrument is determined by its corresponding attribute:

The measurement type must be one of byte, short, int, long, float, double, or decimal.

The measurement parameter is the parameter decorated with [InstrumentMeasurement]. For non-auto-increment instruments, if no parameter is decorated the first parameter with a valid measurement type that is not a [Tag]/[Baggage] is used.

  • [AutoCounter] and [Counter(AutoIncrement = true)] increment by 1 each time the method is called and must not declare a measurement parameter (TSG4002).
  • [Counter], [Histogram], and [UpDownCounter] require a measurement value (TSG4004).
  • Observable instruments must declare a Func<T> parameter (TSG4005).
[Meter]
interface IMeterTelemetry
{
[AutoCounter]
void AutoIncrementMeter([Tag]string someValue);
[Counter(AutoIncrement = true)]
void AutoIncrementCounterMeter([Tag]string someValue);
[Counter]
void CounterMeter([InstrumentMeasurement]int measurement, [Tag]float someValue);
[Histogram]
void HistogramMeter([InstrumentMeasurement]int measurement, [Tag]int someValue, [Tag]bool anotherValue);
[UpDownCounter]
void UpDownCounterMeter([InstrumentMeasurement]decimal measurement, [Tag]byte someValue);
}

Observable instruments always take a System.Func<> parameter with one of the following shapes:

  • Any supported measurement type: byte, short, int, long, float, double, or decimal
  • Measurement<T> where T is a supported measurement type
  • IEnumerable<Measurement<T>> where T is a supported measurement type
[ObservableCounter]
void Counter(Func<int> func);
[ObservableGauge]
void Gauge(Func<Measurement<int>> func);
[ObservableUpDownCounter]
void UpDownCounter(Func<IEnumerable<Measurement<int>>> func);

Other parameters on the method are used as tags. This is implicit for non-measurement parameters, but can also be made explicit with [Tag]. When there are four or more tags, the generated code uses a stack-allocated TagList.

Defines and controls the generation of meters and instruments on an interface, overriding assembly-level defaults.

PropertyTypeDescription
Namestring?The name of the meter, used to group instruments. If not specified, MeterGenerationAttribute.MeterName is used, then the assembly name. Default null.
InstrumentPrefixstring?The prefix used when generating instrument names. Default null.
IncludeAssemblyInstrumentPrefixboolWhether the MeterGenerationAttribute.InstrumentPrefix prefix is included in instrument names. Default true.
LowercaseInstrumentNameboolWhether instrument names (including prefix) are lower-cased. Default true.
LowercaseTagKeysboolWhether tag names are lower-cased. Default true.

Controls meter and instrument defaults at the assembly level.

PropertyTypeDescription
MeterNamestring?The default meter name when none is defined on an interface. Available on construction.
MeterNameGenerationTypeMeterNameGenerationTypeWhether the meter name is used as a dot-separated prefix for instrument names. OpenTelemetry (0) adds the prefix; DotNet (1, default) does not. Available on construction.
InstrumentPrefixstring?The prefix used when generating instrument names. Default null.
InstrumentSeparatorstringThe separator used when generating prefixes. Default ..
LowercaseInstrumentNameboolWhether instrument names (including prefix) are lower-cased. Default true.
LowercaseTagKeysboolWhether tag names are lower-cased. Default true.

Marks the parameter used as the instrument measurement value. Not supported with [AutoCounter] or [Counter(AutoIncrement = true)].

Creates a Counter<int> that increments by 1 per call.

PropertyTypeDescription
Namestring?Instrument name; defaults to the method name. Available on construction. Default null.
Unitstring?The unit used during meter generation. Available on construction. Default null.
Descriptionstring?The description used during meter generation. Available on construction. Default null.

Creates a Counter<T>.

PropertyTypeDescription
AutoIncrementboolWhen true, generates an auto-incrementing counter instead of accepting a measurement value from a parameter. Available on construction. Default false.
Namestring?Instrument name; defaults to the method name. Available on construction. Default null.
Unitstring?The unit used during meter generation. Available on construction. Default null.
Descriptionstring?The description used during meter generation. Available on construction. Default null.

When AutoIncrement is true and [InstrumentMeasurement] is used, the TSG4002 diagnostic is raised.

Create a Histogram<T> or UpDownCounter<T>. Both share the Name, Unit, and Description properties described above.

[ObservableCounter], [ObservableGauge], and [ObservableUpDownCounter] share the Name, Unit, and Description properties, plus:

PropertyTypeDescription
ThrowOnAlreadyInitializedboolWhether the method throws when called more than once. Available on construction. Default false.