-
Notifications
You must be signed in to change notification settings - Fork 855
Description
Background and motivation
I'm trying to define OpenTelemetry Weaver semantic conventions for my telemetry and generate csharp code from them.
My conventions looks like:
groups:
- id: metric.warehouse.conveyor.processed
type: metric
metric_name: warehouse.conveyor.processed.total
stability: stable
brief: "Total number of items processed by the conveyor"
instrument: counter
unit: "{item}"
attributes:
- id: warehouse.conveyor.id
type: string
stability: stable
brief: "The conveyor unique identifier."
requirement_level: required
- id: warehouse.conveyor.error
type: string
stability: stable
brief: "The error message."
requirement_level:
conditionally_required: if applicableI use Jinja templating (defined here) to transform that specification into a csharp class utilizes metric source generation feature. Essentially, the template generated code looks like:
public static partial class Metrics
{
public struct MetricWarehouseConveyorProcessedTags
{
[global::Microsoft.Extensions.Diagnostics.Metrics.TagName("warehouse_conveyor_id")]
public required string WarehouseConveyorId { get; set; }
[global::Microsoft.Extensions.Diagnostics.Metrics.TagName("warehouse_conveyor_error")]
public string WarehouseConveyorError { get; set; }
}
[global::Microsoft.Extensions.Diagnostics.Metrics.Counter<double>(typeof(MetricWarehouseConveyorProcessedTags), Name = "warehouse_conveyor_processed_total")]
public static partial MetricWarehouseConveyorProcessedMetric CreateMetricWarehouseConveyorProcessed(Meter meter);
}I use the required keyword to enforce mandatory properties.
However the source generator produces the following code:
public void Add(double value, global::Application.Metrics.MetricWarehouseConveyorProcessedTags o)
{
var tagList = new global::System.Diagnostics.TagList
{
new global::System.Collections.Generic.KeyValuePair<string, object?>("warehouse_conveyor_id", o.WarehouseConveyorId!),
new global::System.Collections.Generic.KeyValuePair<string, object?>("warehouse_conveyor_error", o.WarehouseConveyorError!),
};
_counter.Add(value, tagList);
}
Even if WarehouseConveyorError is not set, null is sent to the metrics backend.
I would like to propose an API to control the behavior of sending (or not sending) default values through the metric processing pipeline.
API Proposal
public sealed class TagNameAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="TagNameAttribute"/> class.
/// </summary>
/// <param name="name">Tag name.</param>
- public TagNameAttribute(string name, bool optional = true)
+ public TagNameAttribute(string name, bool optional = true)
{
Name = name;
+ Optional = optional;
}
/// <summary>
/// Gets the name of the tag.
/// </summary>
public string Name { get; }
+ /// <summary>
+ /// Tag is optional and will be populated only if value differs from default.
+ /// </summary>
+ public bool Optional { get; }
}A new generated code may look like the following:
public void Add(double value, global::Application.Metrics.MetricWarehouseConveyorProcessedTags o)
{
var tagList = new global::System.Diagnostics.TagList
{
// Pass only the required in obj initializer
new global::System.Collections.Generic.KeyValuePair<string, object?>("warehouse_conveyor_id", o.WarehouseConveyorId!),
};
// Conditionally populate optional tags
if (o.WarehouseConveyorError != default)
{
tagList.Add(new global::System.Collections.Generic.KeyValuePair<string, object?>("warehouse_conveyor_error", o.WarehouseConveyorError!));
}
_counter.Add(value, tagList);
}API Usage
public struct MetricWarehouseConveyorProcessedTags
{
[TagName("warehouse_conveyor_id")]
public required string WarehouseConveyorId { get; set; }
[TagName("warehouse_conveyor_error", optional: true)]
public string WarehouseConveyorError { get; set; }
}Alternative Designs
Alternatively, the code generator could emit the mentioned if (o.WarehouseConveyorError != default) statement depending on the property's nullability. However, this would be a breaking change.
Risks
Performance implications: to be determined.