datadog-statsd-schema 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +20 -0
- data/.rubocop_todo.yml +227 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +360 -0
- data/Rakefile +29 -0
- data/examples/README.md +44 -0
- data/examples/schema_emitter.png +0 -0
- data/examples/schema_emitter.rb +54 -0
- data/examples/shared.rb +43 -0
- data/examples/simple_emitter.rb +23 -0
- data/exe/datadog-statsd-schema +3 -0
- data/lib/datadog/statsd/emitter.rb +471 -0
- data/lib/datadog/statsd/schema/errors.rb +35 -0
- data/lib/datadog/statsd/schema/metric_definition.rb +106 -0
- data/lib/datadog/statsd/schema/namespace.rb +200 -0
- data/lib/datadog/statsd/schema/schema_builder.rb +227 -0
- data/lib/datadog/statsd/schema/tag_definition.rb +71 -0
- data/lib/datadog/statsd/schema/version.rb +9 -0
- data/lib/datadog/statsd/schema.rb +71 -0
- metadata +192 -0
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry-struct"
|
4
|
+
require "dry-types"
|
5
|
+
|
6
|
+
module Datadog
|
7
|
+
class Statsd
|
8
|
+
module Schema
|
9
|
+
class MetricDefinition < Dry::Struct
|
10
|
+
# Include the types module for easier access
|
11
|
+
module Types
|
12
|
+
include Dry.Types()
|
13
|
+
end
|
14
|
+
|
15
|
+
# Valid metric types in StatsD
|
16
|
+
VALID_METRIC_TYPES = %i[counter gauge histogram distribution timing set].freeze
|
17
|
+
|
18
|
+
attribute :name, Types::Strict::Symbol
|
19
|
+
attribute :type, Types::Strict::Symbol.constrained(included_in: VALID_METRIC_TYPES)
|
20
|
+
attribute :description, Types::String.optional.default(nil)
|
21
|
+
attribute :allowed_tags, Types::Array.of(Types::Symbol).default([].freeze)
|
22
|
+
attribute :required_tags, Types::Array.of(Types::Symbol).default([].freeze)
|
23
|
+
attribute :inherit_tags, Types::String.optional.default(nil)
|
24
|
+
attribute :units, Types::String.optional.default(nil)
|
25
|
+
attribute :namespace, Types::Strict::Symbol.optional.default(nil)
|
26
|
+
|
27
|
+
# Get the full metric name including namespace path
|
28
|
+
def full_name(namespace_path = [])
|
29
|
+
return name.to_s if namespace_path.empty?
|
30
|
+
|
31
|
+
"#{namespace_path.join(".")}.#{name}"
|
32
|
+
end
|
33
|
+
|
34
|
+
# Check if a tag is allowed for this metric
|
35
|
+
def allows_tag?(tag_name)
|
36
|
+
tag_symbol = tag_name.to_sym
|
37
|
+
allowed_tags.empty? || allowed_tags.include?(tag_symbol)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Check if a tag is required for this metric
|
41
|
+
def requires_tag?(tag_name)
|
42
|
+
tag_symbol = tag_name.to_sym
|
43
|
+
required_tags.include?(tag_symbol)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Get all missing required tags from a provided tag set
|
47
|
+
def missing_required_tags(provided_tags)
|
48
|
+
provided_tag_symbols = provided_tags.keys.map(&:to_sym)
|
49
|
+
required_tags - provided_tag_symbols
|
50
|
+
end
|
51
|
+
|
52
|
+
# Get all invalid tags from a provided tag set
|
53
|
+
def invalid_tags(provided_tags)
|
54
|
+
return [] if allowed_tags.empty? # No restrictions
|
55
|
+
|
56
|
+
provided_tag_symbols = provided_tags.keys.map(&:to_sym)
|
57
|
+
provided_tag_symbols - allowed_tags
|
58
|
+
end
|
59
|
+
|
60
|
+
# Validate a complete tag set for this metric
|
61
|
+
def valid_tags?(provided_tags)
|
62
|
+
missing_required_tags(provided_tags).empty? && invalid_tags(provided_tags).empty?
|
63
|
+
end
|
64
|
+
|
65
|
+
# Check if this is a timing-based metric
|
66
|
+
def timing_metric?
|
67
|
+
%i[timing distribution histogram].include?(type)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Check if this is a counting metric
|
71
|
+
def counting_metric?
|
72
|
+
%i[counter].include?(type)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Check if this is a gauge metric
|
76
|
+
def gauge_metric?
|
77
|
+
type == :gauge
|
78
|
+
end
|
79
|
+
|
80
|
+
# Check if this is a set metric
|
81
|
+
def set_metric?
|
82
|
+
type == :set
|
83
|
+
end
|
84
|
+
|
85
|
+
# Get effective tags by merging with inherited tags if present
|
86
|
+
def effective_allowed_tags(schema_registry = nil)
|
87
|
+
return allowed_tags unless inherit_tags && schema_registry
|
88
|
+
|
89
|
+
inherited_metric = schema_registry.find_metric(inherit_tags)
|
90
|
+
return allowed_tags unless inherited_metric
|
91
|
+
|
92
|
+
(inherited_metric.effective_allowed_tags(schema_registry) + allowed_tags).uniq
|
93
|
+
end
|
94
|
+
|
95
|
+
def effective_required_tags(schema_registry = nil)
|
96
|
+
return required_tags unless inherit_tags && schema_registry
|
97
|
+
|
98
|
+
inherited_metric = schema_registry.find_metric(inherit_tags)
|
99
|
+
return required_tags unless inherited_metric
|
100
|
+
|
101
|
+
(inherited_metric.effective_required_tags(schema_registry) + required_tags).uniq
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry-struct"
|
4
|
+
require "dry-types"
|
5
|
+
require_relative "tag_definition"
|
6
|
+
require_relative "metric_definition"
|
7
|
+
|
8
|
+
module Datadog
|
9
|
+
class Statsd
|
10
|
+
module Schema
|
11
|
+
class Namespace < Dry::Struct
|
12
|
+
# Include the types module for easier access
|
13
|
+
module Types
|
14
|
+
include Dry.Types()
|
15
|
+
end
|
16
|
+
|
17
|
+
attribute :name, Types::Strict::Symbol
|
18
|
+
attribute :tags, Types::Hash.map(Types::Symbol, TagDefinition).default({}.freeze)
|
19
|
+
attribute :metrics, Types::Hash.map(Types::Symbol, MetricDefinition).default({}.freeze)
|
20
|
+
attribute :namespaces, Types::Hash.map(Types::Symbol, Namespace).default({}.freeze)
|
21
|
+
attribute :description, Types::String.optional.default(nil)
|
22
|
+
|
23
|
+
# Get the full path of this namespace
|
24
|
+
def full_path(parent_path = [])
|
25
|
+
return [name] if parent_path.empty?
|
26
|
+
|
27
|
+
parent_path + [name]
|
28
|
+
end
|
29
|
+
|
30
|
+
# Find a metric by name within this namespace
|
31
|
+
def find_metric(metric_name)
|
32
|
+
metric_symbol = metric_name.to_sym
|
33
|
+
metrics[metric_symbol]
|
34
|
+
end
|
35
|
+
|
36
|
+
# Find a tag definition by name within this namespace
|
37
|
+
def find_tag(tag_name)
|
38
|
+
tag_symbol = tag_name.to_sym
|
39
|
+
tags[tag_symbol]
|
40
|
+
end
|
41
|
+
|
42
|
+
# Find a nested namespace by name
|
43
|
+
def find_namespace(namespace_name)
|
44
|
+
namespace_symbol = namespace_name.to_sym
|
45
|
+
namespaces[namespace_symbol]
|
46
|
+
end
|
47
|
+
|
48
|
+
# Add a new metric to this namespace
|
49
|
+
def add_metric(metric_definition)
|
50
|
+
new(metrics: metrics.merge(metric_definition.name => metric_definition))
|
51
|
+
end
|
52
|
+
|
53
|
+
# Add a new tag definition to this namespace
|
54
|
+
def add_tag(tag_definition)
|
55
|
+
new(tags: tags.merge(tag_definition.name => tag_definition))
|
56
|
+
end
|
57
|
+
|
58
|
+
# Add a nested namespace
|
59
|
+
def add_namespace(namespace)
|
60
|
+
new(namespaces: namespaces.merge(namespace.name => namespace))
|
61
|
+
end
|
62
|
+
|
63
|
+
# Get all metric names in this namespace (not including nested)
|
64
|
+
def metric_names
|
65
|
+
metrics.keys
|
66
|
+
end
|
67
|
+
|
68
|
+
# Get all tag names in this namespace
|
69
|
+
def tag_names
|
70
|
+
tags.keys
|
71
|
+
end
|
72
|
+
|
73
|
+
# Get all nested namespace names
|
74
|
+
def namespace_names
|
75
|
+
namespaces.keys
|
76
|
+
end
|
77
|
+
|
78
|
+
# Check if this namespace contains a metric
|
79
|
+
def has_metric?(metric_name)
|
80
|
+
metric_symbol = metric_name.to_sym
|
81
|
+
metrics.key?(metric_symbol)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Check if this namespace contains a tag definition
|
85
|
+
def has_tag?(tag_name)
|
86
|
+
tag_symbol = tag_name.to_sym
|
87
|
+
tags.key?(tag_symbol)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Check if this namespace contains a nested namespace
|
91
|
+
def has_namespace?(namespace_name)
|
92
|
+
namespace_symbol = namespace_name.to_sym
|
93
|
+
namespaces.key?(namespace_symbol)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Get all metrics recursively including from nested namespaces
|
97
|
+
def all_metrics(path = [])
|
98
|
+
# Filter out :root from the path to avoid it appearing in metric names
|
99
|
+
current_path = path + [name]
|
100
|
+
filtered_path = current_path.reject { |part| part == :root }
|
101
|
+
result = {}
|
102
|
+
|
103
|
+
# Add metrics from this namespace
|
104
|
+
metrics.each do |_metric_name, metric_def|
|
105
|
+
full_metric_name = metric_def.full_name(filtered_path)
|
106
|
+
result[full_metric_name] = {
|
107
|
+
definition: metric_def,
|
108
|
+
namespace_path: filtered_path,
|
109
|
+
namespace: self
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
# Add metrics from nested namespaces recursively
|
114
|
+
namespaces.each do |_, nested_namespace|
|
115
|
+
result.merge!(nested_namespace.all_metrics(current_path))
|
116
|
+
end
|
117
|
+
|
118
|
+
result
|
119
|
+
end
|
120
|
+
|
121
|
+
# Get all tag definitions including inherited from parent namespaces
|
122
|
+
def effective_tags(parent_tags = {})
|
123
|
+
parent_tags.merge(tags)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Validate that all tag references in metrics exist
|
127
|
+
def validate_tag_references
|
128
|
+
errors = []
|
129
|
+
|
130
|
+
metrics.each do |metric_name, metric_def|
|
131
|
+
# Check allowed tags
|
132
|
+
metric_def.allowed_tags.each do |tag_name|
|
133
|
+
errors << "Metric #{metric_name} references unknown tag: #{tag_name}" unless has_tag?(tag_name)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Check required tags
|
137
|
+
metric_def.required_tags.each do |tag_name|
|
138
|
+
errors << "Metric #{metric_name} requires unknown tag: #{tag_name}" unless has_tag?(tag_name)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Validate nested namespaces recursively
|
143
|
+
namespaces.each do |_, nested_namespace|
|
144
|
+
errors.concat(nested_namespace.validate_tag_references)
|
145
|
+
end
|
146
|
+
|
147
|
+
errors
|
148
|
+
end
|
149
|
+
|
150
|
+
# Find metric by path (e.g., "request.duration" within web namespace)
|
151
|
+
def find_metric_by_path(path)
|
152
|
+
parts = path.split(".")
|
153
|
+
|
154
|
+
if parts.length == 1
|
155
|
+
# Single part, look for metric in this namespace
|
156
|
+
find_metric(parts.first)
|
157
|
+
else
|
158
|
+
# Multiple parts, navigate to nested namespace
|
159
|
+
namespace_name = parts.first
|
160
|
+
remaining_path = parts[1..-1].join(".")
|
161
|
+
|
162
|
+
nested_namespace = find_namespace(namespace_name)
|
163
|
+
return nil unless nested_namespace
|
164
|
+
|
165
|
+
nested_namespace.find_metric_by_path(remaining_path)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Get namespace by path (e.g., "web.request")
|
170
|
+
def find_namespace_by_path(path)
|
171
|
+
return self if path.empty?
|
172
|
+
|
173
|
+
parts = path.split(".")
|
174
|
+
|
175
|
+
if parts.length == 1
|
176
|
+
find_namespace(parts.first)
|
177
|
+
else
|
178
|
+
namespace_name = parts.first
|
179
|
+
remaining_path = parts[1..-1].join(".")
|
180
|
+
|
181
|
+
nested_namespace = find_namespace(namespace_name)
|
182
|
+
return nil unless nested_namespace
|
183
|
+
|
184
|
+
nested_namespace.find_namespace_by_path(remaining_path)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Count total metrics including nested namespaces
|
189
|
+
def total_metrics_count
|
190
|
+
metrics.count + namespaces.values.sum(&:total_metrics_count)
|
191
|
+
end
|
192
|
+
|
193
|
+
# Count total namespaces including nested
|
194
|
+
def total_namespaces_count
|
195
|
+
namespaces.count + namespaces.values.sum(&:total_namespaces_count)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,227 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "namespace"
|
4
|
+
require_relative "tag_definition"
|
5
|
+
require_relative "metric_definition"
|
6
|
+
|
7
|
+
module Datadog
|
8
|
+
class Statsd
|
9
|
+
module Schema
|
10
|
+
class SchemaBuilder
|
11
|
+
attr_reader :transformers, :root_namespace
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@transformers = {}
|
15
|
+
@root_namespace = Namespace.new(name: :root)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Define transformers that can be used by tag definitions
|
19
|
+
def transformers(&)
|
20
|
+
return @transformers unless block_given?
|
21
|
+
|
22
|
+
TransformerBuilder.new(@transformers).instance_eval(&)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Define a namespace
|
26
|
+
def namespace(name, &)
|
27
|
+
builder = NamespaceBuilder.new(name, @transformers)
|
28
|
+
builder.instance_eval(&) if block_given?
|
29
|
+
namespace_def = builder.build
|
30
|
+
|
31
|
+
@root_namespace = @root_namespace.add_namespace(namespace_def)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Build the final schema (returns the root namespace)
|
35
|
+
def build
|
36
|
+
@root_namespace
|
37
|
+
end
|
38
|
+
|
39
|
+
# Validate the schema for consistency
|
40
|
+
def validate!
|
41
|
+
errors = @root_namespace.validate_tag_references
|
42
|
+
raise SchemaError, "Schema validation failed: #{errors.join(", ")}" unless errors.empty?
|
43
|
+
end
|
44
|
+
|
45
|
+
# Helper class for building transformers
|
46
|
+
class TransformerBuilder
|
47
|
+
def initialize(transformers)
|
48
|
+
@transformers = transformers
|
49
|
+
end
|
50
|
+
|
51
|
+
def method_missing(name, proc = nil, &block)
|
52
|
+
@transformers[name.to_sym] = proc || block
|
53
|
+
end
|
54
|
+
|
55
|
+
def respond_to_missing?(_name, _include_private = false)
|
56
|
+
true
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Helper class for building namespaces
|
61
|
+
class NamespaceBuilder
|
62
|
+
attr_reader :name, :transformers, :tags, :metrics, :namespaces, :description
|
63
|
+
|
64
|
+
def initialize(name, transformers = {})
|
65
|
+
@name = name.to_sym
|
66
|
+
@transformers = transformers
|
67
|
+
@tags = {}
|
68
|
+
@metrics = {}
|
69
|
+
@namespaces = {}
|
70
|
+
@description = nil
|
71
|
+
end
|
72
|
+
|
73
|
+
# Set description for this namespace
|
74
|
+
def description(desc)
|
75
|
+
@description = desc
|
76
|
+
end
|
77
|
+
|
78
|
+
# Define tags for this namespace
|
79
|
+
def tags(&)
|
80
|
+
TagsBuilder.new(@tags, @transformers).instance_eval(&)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Define metrics for this namespace
|
84
|
+
def metrics(&)
|
85
|
+
MetricsBuilder.new(@metrics, @transformers).instance_eval(&)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Define nested namespace
|
89
|
+
def namespace(name, &)
|
90
|
+
builder = NamespaceBuilder.new(name, @transformers)
|
91
|
+
builder.instance_eval(&) if block_given?
|
92
|
+
@namespaces[name.to_sym] = builder.build
|
93
|
+
end
|
94
|
+
|
95
|
+
def build
|
96
|
+
Namespace.new(
|
97
|
+
name: @name,
|
98
|
+
description: @description,
|
99
|
+
tags: @tags,
|
100
|
+
metrics: @metrics,
|
101
|
+
namespaces: @namespaces
|
102
|
+
)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Helper class for building tags within a namespace
|
107
|
+
class TagsBuilder
|
108
|
+
def initialize(tags, transformers)
|
109
|
+
@tags = tags
|
110
|
+
@transformers = transformers
|
111
|
+
end
|
112
|
+
|
113
|
+
def tag(name, **options)
|
114
|
+
tag_def = TagDefinition.new(
|
115
|
+
name: name.to_sym,
|
116
|
+
values: options[:values],
|
117
|
+
type: options[:type] || :string,
|
118
|
+
transform: Array(options[:transform] || []),
|
119
|
+
validate: options[:validate],
|
120
|
+
namespace: @current_namespace
|
121
|
+
)
|
122
|
+
@tags[name.to_sym] = tag_def
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Helper class for building metrics within a namespace
|
127
|
+
class MetricsBuilder
|
128
|
+
def initialize(metrics, transformers)
|
129
|
+
@metrics = metrics
|
130
|
+
@transformers = transformers
|
131
|
+
@current_namespace = nil
|
132
|
+
end
|
133
|
+
|
134
|
+
# Define a nested namespace for metrics
|
135
|
+
def namespace(name, &)
|
136
|
+
@current_namespace = name.to_sym
|
137
|
+
instance_eval(&)
|
138
|
+
@current_namespace = nil
|
139
|
+
end
|
140
|
+
|
141
|
+
# Define individual metric types
|
142
|
+
%i[counter gauge histogram distribution timing set].each do |metric_type|
|
143
|
+
define_method(metric_type) do |name, **options, &block|
|
144
|
+
metric_name = @current_namespace ? :"#{@current_namespace}_#{name}" : name.to_sym
|
145
|
+
|
146
|
+
metric_def = MetricDefinition.new(
|
147
|
+
name: metric_name,
|
148
|
+
type: metric_type,
|
149
|
+
description: options[:description],
|
150
|
+
allowed_tags: extract_allowed_tags(options),
|
151
|
+
required_tags: extract_required_tags(options),
|
152
|
+
inherit_tags: options[:inherit_tags],
|
153
|
+
units: options[:units],
|
154
|
+
namespace: @current_namespace
|
155
|
+
)
|
156
|
+
|
157
|
+
unless block.nil?
|
158
|
+
metric_builder = MetricBuilder.new(metric_def)
|
159
|
+
metric_builder.instance_eval(&block)
|
160
|
+
metric_def = metric_builder.build
|
161
|
+
end
|
162
|
+
|
163
|
+
@metrics[metric_name] = metric_def
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
def extract_allowed_tags(options)
|
170
|
+
tags_option = options[:tags]
|
171
|
+
return [] unless tags_option
|
172
|
+
|
173
|
+
if tags_option.is_a?(Hash)
|
174
|
+
Array(tags_option[:allowed] || []).map(&:to_sym)
|
175
|
+
else
|
176
|
+
Array(tags_option).map(&:to_sym)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def extract_required_tags(options)
|
181
|
+
tags_option = options[:tags]
|
182
|
+
return [] unless tags_option
|
183
|
+
|
184
|
+
if tags_option.is_a?(Hash)
|
185
|
+
Array(tags_option[:required] || []).map(&:to_sym)
|
186
|
+
else
|
187
|
+
[]
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Helper class for building individual metrics with block syntax
|
193
|
+
class MetricBuilder
|
194
|
+
def initialize(metric_def)
|
195
|
+
@metric_def = metric_def
|
196
|
+
end
|
197
|
+
|
198
|
+
def description(desc)
|
199
|
+
@metric_def = @metric_def.new(description: desc)
|
200
|
+
end
|
201
|
+
|
202
|
+
def tags(**options)
|
203
|
+
allowed = Array(options[:allowed] || []).map(&:to_sym)
|
204
|
+
required = Array(options[:required] || []).map(&:to_sym)
|
205
|
+
|
206
|
+
@metric_def = @metric_def.new(
|
207
|
+
allowed_tags: allowed,
|
208
|
+
required_tags: required
|
209
|
+
)
|
210
|
+
end
|
211
|
+
|
212
|
+
def units(unit_name)
|
213
|
+
@metric_def = @metric_def.new(units: unit_name)
|
214
|
+
end
|
215
|
+
|
216
|
+
def inherit_tags(metric_path)
|
217
|
+
@metric_def = @metric_def.new(inherit_tags: metric_path)
|
218
|
+
end
|
219
|
+
|
220
|
+
def build
|
221
|
+
@metric_def
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry-struct"
|
4
|
+
require "dry-types"
|
5
|
+
|
6
|
+
module Datadog
|
7
|
+
class Statsd
|
8
|
+
module Schema
|
9
|
+
class TagDefinition < Dry::Struct
|
10
|
+
# Include the types module for easier access
|
11
|
+
module Types
|
12
|
+
include Dry.Types()
|
13
|
+
end
|
14
|
+
|
15
|
+
attribute :name, Types::Strict::Symbol
|
16
|
+
attribute :values, Types::Any.optional.default(nil) # Allow any type: Array, Regexp, Proc, or single value
|
17
|
+
attribute :type, Types::Strict::Symbol.default(:string)
|
18
|
+
attribute :transform, Types::Array.of(Types::Symbol).default([].freeze)
|
19
|
+
attribute :validate, Types::Any.optional.default(nil) # Proc for custom validation
|
20
|
+
attribute :namespace, Types::Strict::Symbol.optional.default(nil)
|
21
|
+
|
22
|
+
# Check if a value is allowed for this tag
|
23
|
+
def allows_value?(value)
|
24
|
+
return true if values.nil? # No restrictions
|
25
|
+
|
26
|
+
case values
|
27
|
+
when Array
|
28
|
+
values.include?(value) || values.include?(value.to_s) || values.include?(value.to_sym)
|
29
|
+
when Regexp
|
30
|
+
values.match?(value.to_s)
|
31
|
+
when Proc
|
32
|
+
values.call(value)
|
33
|
+
else
|
34
|
+
values == value
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Apply transformations to a value
|
39
|
+
def transform_value(value, transformers = {})
|
40
|
+
return value if transform.empty?
|
41
|
+
|
42
|
+
transform.reduce(value) do |val, transformer_name|
|
43
|
+
transformer = transformers[transformer_name]
|
44
|
+
transformer ? transformer.call(val) : val
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Validate a value using custom validation if present
|
49
|
+
def valid_value?(value, transformers = {})
|
50
|
+
transformed_value = transform_value(value, transformers)
|
51
|
+
|
52
|
+
# Apply type validation
|
53
|
+
case type
|
54
|
+
when :integer
|
55
|
+
return false unless transformed_value.is_a?(Integer) || transformed_value.to_s.match?(/^\d+$/)
|
56
|
+
when :string
|
57
|
+
# strings are generally permissive
|
58
|
+
when :symbol
|
59
|
+
# symbols are generally permissive, will be converted
|
60
|
+
end
|
61
|
+
|
62
|
+
# Apply custom validation if present
|
63
|
+
return validate.call(transformed_value) if validate.is_a?(Proc)
|
64
|
+
|
65
|
+
# Apply value restrictions
|
66
|
+
allows_value?(transformed_value)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "datadog/statsd"
|
4
|
+
require "active_support/core_ext/module/delegation"
|
5
|
+
|
6
|
+
require_relative "schema/version"
|
7
|
+
require_relative "schema/errors"
|
8
|
+
require_relative "schema/tag_definition"
|
9
|
+
require_relative "schema/metric_definition"
|
10
|
+
require_relative "schema/namespace"
|
11
|
+
require_relative "schema/schema_builder"
|
12
|
+
require_relative "emitter"
|
13
|
+
|
14
|
+
module Datadog
|
15
|
+
class << self
|
16
|
+
def emitter(...)
|
17
|
+
::Datadog::Statsd::Emitter.new(...)
|
18
|
+
end
|
19
|
+
|
20
|
+
def schema(...)
|
21
|
+
::Datadog::Statsd::Schema.new(...)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Statsd
|
26
|
+
module Schema
|
27
|
+
class Error < StandardError
|
28
|
+
end
|
29
|
+
|
30
|
+
class << self
|
31
|
+
attr_accessor :in_test
|
32
|
+
end
|
33
|
+
|
34
|
+
self.in_test = false
|
35
|
+
|
36
|
+
# Create a new schema definition
|
37
|
+
def self.new(&)
|
38
|
+
builder = SchemaBuilder.new
|
39
|
+
builder.instance_eval(&) if block_given?
|
40
|
+
builder.build
|
41
|
+
end
|
42
|
+
|
43
|
+
# Load schema from a file
|
44
|
+
def self.load_file(path)
|
45
|
+
builder = SchemaBuilder.new
|
46
|
+
builder.instance_eval(File.read(path), path)
|
47
|
+
builder.build
|
48
|
+
end
|
49
|
+
|
50
|
+
# Configure the global schema
|
51
|
+
def self.configure
|
52
|
+
yield configuration
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.configuration
|
56
|
+
@configuration ||= Configuration.new
|
57
|
+
end
|
58
|
+
|
59
|
+
# Configuration class for global settings
|
60
|
+
class Configuration
|
61
|
+
attr_accessor :statsd, :schema, :tags
|
62
|
+
|
63
|
+
def initialize
|
64
|
+
@statsd = nil
|
65
|
+
@schema = nil
|
66
|
+
@tags = {}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|