mirror of
https://github.com/hanzoai/traces.git
synced 2026-10-30 18:20:41 +00:00
* [Generator] Add DRAIN span_name sanitizer * Fix linter * Fix formatting * Update default manifest * Fix build error in update-fixtures * Remove bad test * Remove misleading comment * Add per-tenant override for drain mode * Do a machines job * Address PR feedback * Address PR review: rename drain_mode config and improve sanitizer - Rename `drain_mode` to `span_name_sanitization` across all config, legacy config, interfaces, and overrides for better user-facing clarity - Add explicit default value "disabled" in RegisterFlagsAndApplyDefaults - Add ValidateSpanNameSanitization function in validation/fields.go - Rename metrics to remove DRAIN reference: `drain_spans_compacted_total` -> `spans_sanitized_total` `post_drain_demand_estimate` -> `post_sanitization_demand_estimate` - Pass configured staleDuration to NewDrainSanitizer instead of hardcoded 15min, consistent with other registry components - Extract "span_name" magic string to labelSpanName constant - Update all tests with new names and signatures * Update manifest * Use empty string as disabled * Update CHANGELOG.md Co-authored-by: Suraj Nath <[email protected]> * Update manifest --------- Co-authored-by: Suraj Nath <[email protected]>
225 lines
8.2 KiB
Go
225 lines
8.2 KiB
Go
package validation
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/grafana/tempo/modules/distributor/usage"
|
|
"github.com/grafana/tempo/modules/generator/processor"
|
|
"github.com/grafana/tempo/modules/generator/registry"
|
|
"github.com/grafana/tempo/pkg/sharedconfig"
|
|
"github.com/grafana/tempo/pkg/spanfilter"
|
|
filterconfig "github.com/grafana/tempo/pkg/spanfilter/config"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/prometheus/common/model"
|
|
"github.com/prometheus/prometheus/util/strutil"
|
|
)
|
|
|
|
var SupportedProcessors = []string{
|
|
processor.ServiceGraphsName,
|
|
processor.SpanMetricsName,
|
|
processor.LocalBlocksName,
|
|
processor.SpanMetricsCountName,
|
|
processor.SpanMetricsLatencyName,
|
|
processor.SpanMetricsSizeName,
|
|
processor.HostInfoName,
|
|
}
|
|
|
|
var SupportedIntrinsicDimensions = []string{processor.DimService, processor.DimSpanName, processor.DimSpanKind, processor.DimStatusCode, processor.DimStatusMessage}
|
|
|
|
var SupportedIntrinsicDimensionsSet map[string]struct{}
|
|
|
|
var SupportedProcessorsSet map[string]struct{}
|
|
|
|
var SupportedSpanNameSanitizationModes = []string{
|
|
registry.SpanNameSanitizationDisabled,
|
|
registry.SpanNameSanitizationDryRun,
|
|
registry.SpanNameSanitizationEnabled,
|
|
}
|
|
|
|
var SupportedSpanNameSanitizationModesSet map[string]struct{}
|
|
|
|
var SupportedHistogramModesSet map[string]struct{}
|
|
|
|
func init() {
|
|
SupportedIntrinsicDimensionsSet = make(map[string]struct{})
|
|
for _, dim := range SupportedIntrinsicDimensions {
|
|
SupportedIntrinsicDimensionsSet[dim] = struct{}{}
|
|
}
|
|
SupportedProcessorsSet = make(map[string]struct{})
|
|
for _, p := range SupportedProcessors {
|
|
SupportedProcessorsSet[p] = struct{}{}
|
|
}
|
|
SupportedHistogramModesSet = make(map[string]struct{})
|
|
for mode := range registry.HistogramModeToValue {
|
|
SupportedHistogramModesSet[mode] = struct{}{}
|
|
}
|
|
SupportedSpanNameSanitizationModesSet = make(map[string]struct{})
|
|
for _, mode := range SupportedSpanNameSanitizationModes {
|
|
SupportedSpanNameSanitizationModesSet[mode] = struct{}{}
|
|
}
|
|
}
|
|
|
|
func ValidateProcessor(processors string) error {
|
|
if _, ok := SupportedProcessorsSet[processors]; !ok {
|
|
return fmt.Errorf("metrics_generator.processor \"%s\" is not a known processor, valid values: %v", processors, SupportedProcessors)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateCollectionInterval(collectionInterval time.Duration) error {
|
|
if collectionInterval < 15*time.Second || collectionInterval > 5*time.Minute {
|
|
return fmt.Errorf("metrics_generator.collection_interval \"%s\" is outside acceptable range of 15s to 5m", collectionInterval.String())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateIngestionTimeRangeSlack(ingestionTimeRangeSlack time.Duration) error {
|
|
if ingestionTimeRangeSlack < 0 || ingestionTimeRangeSlack > 12*time.Hour {
|
|
return fmt.Errorf("metrics_generator.ingestion_time_range_slack \"%s\" is outside acceptable range of 0s to 12h", ingestionTimeRangeSlack.String())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateSpanNameSanitization(mode string) error {
|
|
if _, ok := SupportedSpanNameSanitizationModesSet[mode]; !ok {
|
|
return fmt.Errorf("span_name_sanitization \"%s\" is not valid, valid values: %v", mode, SupportedSpanNameSanitizationModes)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateHistogramMode(mode string) error {
|
|
if _, ok := SupportedHistogramModesSet[mode]; !ok {
|
|
return fmt.Errorf("metrics_generator.generate_native_histograms \"%s\" is not a valid value, valid values: classic, native, both", mode)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateHostInfoHostIdentifiers(hostIdentifiers []string) error {
|
|
if len(hostIdentifiers) == 0 {
|
|
return errors.New("at least one value must be provided in host_identifiers")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateHostInfoMetricName(metricName string) error {
|
|
if !model.UTF8Validation.IsValidLabelName(metricName) {
|
|
return errors.New("metric_name is invalid")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateDimensions(dimensions []string, intrinsicDimensions []string, dimensionMappings []sharedconfig.DimensionMappings, sanitizeFn SanitizeFn) error {
|
|
seen := make(map[string]string) // sanitized label -> original source
|
|
|
|
for _, d := range intrinsicDimensions {
|
|
seen[d] = fmt.Sprintf("intrinsic dimension %q", d)
|
|
}
|
|
|
|
for _, d := range dimensions {
|
|
sanitized := SanitizeLabelNameWithCollisions(d, SupportedIntrinsicDimensionsSet, sanitizeFn)
|
|
seen[sanitized] = fmt.Sprintf("dimension %q", d)
|
|
// we allow collisions like deployment_environment and deployment.environment, this may be a valid use case if tenant is using different SDK for instrumentation
|
|
}
|
|
|
|
for _, m := range dimensionMappings {
|
|
sanitized := SanitizeLabelNameWithCollisions(m.Name, SupportedIntrinsicDimensionsSet, sanitizeFn)
|
|
if source, exists := seen[sanitized]; exists {
|
|
return fmt.Errorf("dimension_mapping %q produces label %q which collides with %s", m.Name, sanitized, source)
|
|
}
|
|
seen[sanitized] = fmt.Sprintf("dimension_mapping %q", m.Name)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func ValidateTraceIDLabelName(traceIDLabelName string) error {
|
|
if traceIDLabelName != SanitizeLabelName(traceIDLabelName) {
|
|
return fmt.Errorf("trace_id_label_name \"%s\" is not a valid Prometheus label name", traceIDLabelName)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateHistogramBuckets(buckets []float64, field string) error {
|
|
for i, bucket := range buckets {
|
|
if i > 0 && bucket <= buckets[i-1] {
|
|
return fmt.Errorf("%s must be strictly increasing: bucket[%d]=%g is <= bucket[%d]=%g", field, i, bucket, i-1, buckets[i-1])
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateNativeHistogramBucketFactor(factor float64) error {
|
|
if factor <= 1 {
|
|
return fmt.Errorf("metrics_generator.native_histogram_bucket_factor must be greater than 1")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateCostAttributionDimensions(dimensions map[string]string) error {
|
|
seenLabels := make(map[string]string)
|
|
|
|
// map is with key=tempo attribute, value=prometheus labelName
|
|
for k, v := range dimensions {
|
|
// build labelName in the similar way as usage.GetBuffersForDimensions
|
|
attr, _ := usage.ParseDimensionKey(k) // extract attr so validate the duplicates with scope prefix
|
|
labelName := v
|
|
if labelName == "" {
|
|
labelName = attr // The dimension is using default mapping, we map it to attribute
|
|
}
|
|
labelName = strutil.SanitizeFullLabelName(labelName) // sanitize label name
|
|
|
|
// check for duplicate prometheus label names.
|
|
// when we have duplicate labelNames, we randomly pick one so validate and don't allow duplicates.
|
|
if originalKey, exists := seenLabels[labelName]; exists {
|
|
return fmt.Errorf("cost_attribution.dimensions has duplicate label name: '%s', both '%s' and '%s' map to it", labelName, originalKey, k)
|
|
}
|
|
seenLabels[labelName] = k // put k as value so we can show configured keys in the error
|
|
|
|
// creating a desc do the complete labelName validation
|
|
desc := prometheus.NewDesc("test_desc", "test desc created for validation", []string{labelName}, nil)
|
|
// try to create a metric and see if there are any error, we use same method in usage.Collect
|
|
_, err := prometheus.NewConstMetric(desc, prometheus.CounterValue, float64(1), labelName)
|
|
if err != nil {
|
|
return fmt.Errorf("cost_attribution.dimensions config has invalid label name: '%s'", labelName)
|
|
}
|
|
}
|
|
|
|
// no errors, we are good.
|
|
return nil
|
|
}
|
|
|
|
func ValidateIntrinsicDimensions(intrinsicDimensions map[string]bool) error {
|
|
for dim := range intrinsicDimensions {
|
|
if _, ok := SupportedIntrinsicDimensionsSet[dim]; !ok {
|
|
return fmt.Errorf("intrinsic dimension \"%s\" is not supported, valid values: %v", dim, SupportedIntrinsicDimensions)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateDimensionMappings(dimensionMappings []sharedconfig.DimensionMappings) error {
|
|
for _, m := range dimensionMappings {
|
|
if m.Name == "" {
|
|
return errors.New("dimension_mappings: name cannot be empty")
|
|
}
|
|
if len(m.SourceLabel) == 0 {
|
|
return fmt.Errorf("dimension_mappings: source_labels cannot be empty for mapping with name %q", m.Name)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateFilterPolicies(policies []filterconfig.FilterPolicy) error {
|
|
// config.ValidateFilterPolicy only does shallow structural validation.
|
|
// NewSpanFilter does full validation including attribute parsing, scope checks, and regex compilation.
|
|
// The filter itself is discarded, we only care about the error because we are checking if the policies are valid.
|
|
_, err := spanfilter.NewSpanFilter(policies)
|
|
return err
|
|
}
|
|
|
|
func ValidateServiceGraphsDimensions(dimensions []string) error {
|
|
return nil
|
|
}
|