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 applicable
I 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.
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:
I 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:
I use the
requiredkeyword to enforce mandatory properties.However the source generator produces the following code:
Even if
WarehouseConveyorErroris not set,nullis 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:
API Usage
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.