statsd-instrument 3.11.2 → 4.0.1
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 +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +5 -0
- data/lib/statsd/instrument/aggregator.rb +3 -3
- data/lib/statsd/instrument/client.rb +38 -24
- data/lib/statsd/instrument/compiled_metric.rb +9 -9
- data/lib/statsd/instrument/datagram.rb +3 -3
- data/lib/statsd/instrument/datagram_builder.rb +12 -12
- data/lib/statsd/instrument/dogstatsd_datagram.rb +11 -11
- data/lib/statsd/instrument/dogstatsd_datagram_builder.rb +1 -1
- data/lib/statsd/instrument/sanitization.rb +34 -0
- data/lib/statsd/instrument/version.rb +1 -1
- data/lib/statsd/instrument.rb +1 -0
- data/test/aggregator_test.rb +68 -100
- data/test/client_test.rb +64 -0
- data/test/newline_normalization_test.rb +262 -0
- data/test/rubocop/singleton_configuration_test.rb +1 -3
- data/test/sanitization_test.rb +49 -0
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c7184b3fb0852d7afea02fa71a292812ef26b491eb94ab2427f603b49f01202c
|
|
4
|
+
data.tar.gz: e40e7aa3d4cb034467b6d59fdfdb3bcbf55a2901e82c48368fd1e904d0aa67c5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e1d01801fcbedc88df3e57bf948e6a4b599b52365e80d55e66ced52d4b308427632d5dc8161614705a7524e9a7f1b56e85e717ef9e3f0509969091b801ec30c2
|
|
7
|
+
data.tar.gz: 97bf3cf218ef6be51d72dc96f067464cc8f21eb0b070be9e48d7097cc4863735f41a58a3ed6967829524b6df64af1d8a1122197fec46944ce8154dcf539a4721
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,14 @@ section below.
|
|
|
6
6
|
|
|
7
7
|
## Unreleased changes
|
|
8
8
|
|
|
9
|
+
## Version 4.0.1
|
|
10
|
+
|
|
11
|
+
- Normalize ASCII whitespace in metric names and prefixes, and newlines (`\n` and `\r`) in tags and set values, including aggregated and compiled metrics.
|
|
12
|
+
|
|
13
|
+
## Version 4.0.0
|
|
14
|
+
|
|
15
|
+
- Make the internal aggregator interface positional-only and injectable through `Client`, with fixed-arity `increment`, `gauge`, and `aggregate_timing` entrypoints for native aggregation backends. Public Client metric methods retain their keyword API.
|
|
16
|
+
|
|
9
17
|
## Version 3.11.2
|
|
10
18
|
|
|
11
19
|
- Widen the `CompiledMetric` tag-combination cache key from 32 to 57 bits, eliminating
|
data/README.md
CHANGED
|
@@ -114,6 +114,11 @@ Please note that since aggregation is an experimental feature, it should be used
|
|
|
114
114
|
## StatsD keys
|
|
115
115
|
|
|
116
116
|
StatsD keys look like 'admin.logins.api.success'. Dots are used as namespace separators.
|
|
117
|
+
Metric names and prefixes normalize `:`, `|`, `@`, and ASCII whitespace (spaces,
|
|
118
|
+
tabs, and line breaks) to `_`, one underscore per character.
|
|
119
|
+
Newlines (`\n`, `\r`) in set values are also replaced with `_`; in array/hash tags,
|
|
120
|
+
newlines are removed alongside `|` and `,`. Other tag/value whitespace is preserved.
|
|
121
|
+
Normalization is silent.
|
|
117
122
|
|
|
118
123
|
## Usage
|
|
119
124
|
|
|
@@ -148,7 +148,7 @@ module StatsD
|
|
|
148
148
|
# @param tags [Hash{String, Symbol => String},Array<String>] The tags to attach to the counter.
|
|
149
149
|
# @param no_prefix [Boolean] If true, the metric will not be prefixed.
|
|
150
150
|
# @return [void]
|
|
151
|
-
def increment(name, value
|
|
151
|
+
def increment(name, value, tags, no_prefix, sample_rate)
|
|
152
152
|
unless thread_healthcheck
|
|
153
153
|
@sink << datagram_builder(no_prefix: no_prefix).c(name, value, sample_rate, tags)
|
|
154
154
|
return
|
|
@@ -224,7 +224,7 @@ module StatsD
|
|
|
224
224
|
end
|
|
225
225
|
end
|
|
226
226
|
|
|
227
|
-
def aggregate_timing(name, value, tags
|
|
227
|
+
def aggregate_timing(name, value, tags, no_prefix, type, sample_rate)
|
|
228
228
|
unless thread_healthcheck
|
|
229
229
|
@sink << datagram_builder(no_prefix: no_prefix).timing_value_packed(
|
|
230
230
|
name, type.to_s, [value], sample_rate, tags
|
|
@@ -249,7 +249,7 @@ module StatsD
|
|
|
249
249
|
do_flush(aggregation_state) if aggregation_state
|
|
250
250
|
end
|
|
251
251
|
|
|
252
|
-
def gauge(name, value, tags
|
|
252
|
+
def gauge(name, value, tags, no_prefix)
|
|
253
253
|
unless thread_healthcheck
|
|
254
254
|
@sink << datagram_builder(no_prefix: no_prefix).g(name, value, CONST_SAMPLE_RATE, tags)
|
|
255
255
|
return
|
|
@@ -19,6 +19,9 @@ module StatsD
|
|
|
19
19
|
# @see StatsD.singleton_client
|
|
20
20
|
# @see #clone_with_options
|
|
21
21
|
class Client
|
|
22
|
+
EMPTY_TAGS = [].freeze
|
|
23
|
+
private_constant :EMPTY_TAGS
|
|
24
|
+
|
|
22
25
|
class << self
|
|
23
26
|
# Instantiates a StatsD::Instrument::Client using configuration values provided in
|
|
24
27
|
# environment variables.
|
|
@@ -31,7 +34,8 @@ module StatsD
|
|
|
31
34
|
default_tags: env.statsd_default_tags,
|
|
32
35
|
implementation: env.statsd_implementation,
|
|
33
36
|
sink: env.default_sink_for_environment,
|
|
34
|
-
datagram_builder_class: datagram_builder_class_for_implementation(implementation)
|
|
37
|
+
datagram_builder_class: datagram_builder_class_for_implementation(implementation),
|
|
38
|
+
aggregator: nil
|
|
35
39
|
)
|
|
36
40
|
new(
|
|
37
41
|
prefix: prefix,
|
|
@@ -42,6 +46,7 @@ module StatsD
|
|
|
42
46
|
datagram_builder_class: datagram_builder_class,
|
|
43
47
|
enable_aggregation: env.experimental_aggregation_enabled?,
|
|
44
48
|
aggregation_flush_interval: env.aggregation_interval,
|
|
49
|
+
aggregator: aggregator,
|
|
45
50
|
)
|
|
46
51
|
end
|
|
47
52
|
|
|
@@ -147,6 +152,16 @@ module StatsD
|
|
|
147
152
|
end
|
|
148
153
|
|
|
149
154
|
# Instantiates a new client.
|
|
155
|
+
#
|
|
156
|
+
# Aggregators use fixed-arity positional +increment+, +gauge+, and
|
|
157
|
+
# +aggregate_timing+ methods. This keeps the aggregation hot path simple
|
|
158
|
+
# and avoids keyword argument forwarding. An injected aggregator is also
|
|
159
|
+
# expected to implement the existing precompiled aggregation methods and
|
|
160
|
+
# +flush+. Calls without tags receive a shared frozen empty array. Other tag
|
|
161
|
+
# values are forwarded as-is; custom backends may require arrays.
|
|
162
|
+
#
|
|
163
|
+
# @param aggregator [#increment, #gauge, #aggregate_timing, nil]
|
|
164
|
+
# Optional external aggregation backend.
|
|
150
165
|
# @see .from_env to instantiate a client using environment variables.
|
|
151
166
|
def initialize(
|
|
152
167
|
prefix: nil,
|
|
@@ -157,7 +172,8 @@ module StatsD
|
|
|
157
172
|
datagram_builder_class: self.class.datagram_builder_class_for_implementation(implementation),
|
|
158
173
|
enable_aggregation: false,
|
|
159
174
|
aggregation_flush_interval: 2.0,
|
|
160
|
-
aggregation_max_context_size: StatsD::Instrument::Aggregator::DEFAULT_MAX_CONTEXT_SIZE
|
|
175
|
+
aggregation_max_context_size: StatsD::Instrument::Aggregator::DEFAULT_MAX_CONTEXT_SIZE,
|
|
176
|
+
aggregator: nil
|
|
161
177
|
)
|
|
162
178
|
@sink = sink
|
|
163
179
|
@datagram_builder_class = datagram_builder_class
|
|
@@ -167,10 +183,11 @@ module StatsD
|
|
|
167
183
|
@default_sample_rate = default_sample_rate
|
|
168
184
|
|
|
169
185
|
@datagram_builder = { false => nil, true => nil }
|
|
170
|
-
@
|
|
186
|
+
@injected_aggregator = aggregator
|
|
187
|
+
@enable_aggregation = enable_aggregation || !aggregator.nil?
|
|
171
188
|
@aggregation_flush_interval = aggregation_flush_interval
|
|
172
189
|
if @enable_aggregation
|
|
173
|
-
@aggregator =
|
|
190
|
+
@aggregator = aggregator ||
|
|
174
191
|
Aggregator.new(
|
|
175
192
|
@sink,
|
|
176
193
|
datagram_builder_class,
|
|
@@ -196,8 +213,8 @@ module StatsD
|
|
|
196
213
|
#
|
|
197
214
|
# - We recommend using `snake_case.metric_names` as naming scheme.
|
|
198
215
|
# - A `.` should be used for namespacing, e.g. `foo.bar.baz`
|
|
199
|
-
# - A metric name should not include
|
|
200
|
-
# The library
|
|
216
|
+
# - A metric name should not include `|`, `@`, `:`, or ASCII whitespace.
|
|
217
|
+
# The library converts each such character to `_`, including in prefixes.
|
|
201
218
|
#
|
|
202
219
|
# @param value [Integer] (default: 1) The value to increment the counter by.
|
|
203
220
|
#
|
|
@@ -223,7 +240,7 @@ module StatsD
|
|
|
223
240
|
return StatsD::Instrument::VOID if sample_rate && !sample?(sample_rate)
|
|
224
241
|
|
|
225
242
|
if @enable_aggregation
|
|
226
|
-
@aggregator.increment(name, value, tags
|
|
243
|
+
@aggregator.increment(name, value, tags || EMPTY_TAGS, no_prefix, sample_rate)
|
|
227
244
|
else
|
|
228
245
|
emit(datagram_builder(no_prefix: no_prefix).c(name, value, sample_rate, tags))
|
|
229
246
|
end
|
|
@@ -357,7 +374,7 @@ module StatsD
|
|
|
357
374
|
end
|
|
358
375
|
|
|
359
376
|
if @enable_aggregation
|
|
360
|
-
@aggregator.aggregate_timing(name, value, tags
|
|
377
|
+
@aggregator.aggregate_timing(name, value, tags || EMPTY_TAGS, no_prefix, :ms, sample_rate)
|
|
361
378
|
return StatsD::Instrument::VOID
|
|
362
379
|
end
|
|
363
380
|
emit(datagram_builder(no_prefix: no_prefix).ms(name, value, sample_rate, tags))
|
|
@@ -379,7 +396,7 @@ module StatsD
|
|
|
379
396
|
# @return [void]
|
|
380
397
|
def gauge(name, value, sample_rate: nil, tags: nil, no_prefix: false)
|
|
381
398
|
if @enable_aggregation
|
|
382
|
-
@aggregator.gauge(name, value, tags
|
|
399
|
+
@aggregator.gauge(name, value, tags || EMPTY_TAGS, no_prefix)
|
|
383
400
|
return StatsD::Instrument::VOID
|
|
384
401
|
end
|
|
385
402
|
|
|
@@ -431,14 +448,7 @@ module StatsD
|
|
|
431
448
|
end
|
|
432
449
|
|
|
433
450
|
if @enable_aggregation
|
|
434
|
-
@aggregator.aggregate_timing(
|
|
435
|
-
name,
|
|
436
|
-
value,
|
|
437
|
-
tags: tags,
|
|
438
|
-
no_prefix: no_prefix,
|
|
439
|
-
type: :d,
|
|
440
|
-
sample_rate: sample_rate,
|
|
441
|
-
)
|
|
451
|
+
@aggregator.aggregate_timing(name, value, tags || EMPTY_TAGS, no_prefix, :d, sample_rate)
|
|
442
452
|
return StatsD::Instrument::VOID
|
|
443
453
|
end
|
|
444
454
|
|
|
@@ -467,7 +477,7 @@ module StatsD
|
|
|
467
477
|
end
|
|
468
478
|
|
|
469
479
|
if @enable_aggregation
|
|
470
|
-
@aggregator.aggregate_timing(name, value, tags
|
|
480
|
+
@aggregator.aggregate_timing(name, value, tags || EMPTY_TAGS, no_prefix, :h, sample_rate)
|
|
471
481
|
return StatsD::Instrument::VOID
|
|
472
482
|
end
|
|
473
483
|
|
|
@@ -508,10 +518,10 @@ module StatsD
|
|
|
508
518
|
@aggregator.aggregate_timing(
|
|
509
519
|
name,
|
|
510
520
|
latency_in_ms,
|
|
511
|
-
tags
|
|
512
|
-
no_prefix
|
|
513
|
-
|
|
514
|
-
sample_rate
|
|
521
|
+
tags || EMPTY_TAGS,
|
|
522
|
+
no_prefix,
|
|
523
|
+
metric_type,
|
|
524
|
+
sample_rate,
|
|
515
525
|
)
|
|
516
526
|
else
|
|
517
527
|
emit(datagram_builder(no_prefix: no_prefix).send(metric_type, name, latency_in_ms, sample_rate, tags))
|
|
@@ -598,7 +608,8 @@ module StatsD
|
|
|
598
608
|
prefix: NO_CHANGE,
|
|
599
609
|
default_sample_rate: NO_CHANGE,
|
|
600
610
|
default_tags: NO_CHANGE,
|
|
601
|
-
datagram_builder_class: NO_CHANGE
|
|
611
|
+
datagram_builder_class: NO_CHANGE,
|
|
612
|
+
aggregator: NO_CHANGE
|
|
602
613
|
)
|
|
603
614
|
client = clone_with_options(
|
|
604
615
|
sink: sink,
|
|
@@ -606,6 +617,7 @@ module StatsD
|
|
|
606
617
|
default_sample_rate: default_sample_rate,
|
|
607
618
|
default_tags: default_tags,
|
|
608
619
|
datagram_builder_class: datagram_builder_class,
|
|
620
|
+
aggregator: aggregator,
|
|
609
621
|
)
|
|
610
622
|
|
|
611
623
|
yield(client)
|
|
@@ -616,7 +628,8 @@ module StatsD
|
|
|
616
628
|
prefix: NO_CHANGE,
|
|
617
629
|
default_sample_rate: NO_CHANGE,
|
|
618
630
|
default_tags: NO_CHANGE,
|
|
619
|
-
datagram_builder_class: NO_CHANGE
|
|
631
|
+
datagram_builder_class: NO_CHANGE,
|
|
632
|
+
aggregator: NO_CHANGE
|
|
620
633
|
)
|
|
621
634
|
self.class.new(
|
|
622
635
|
sink: sink == NO_CHANGE ? @sink : sink,
|
|
@@ -627,6 +640,7 @@ module StatsD
|
|
|
627
640
|
datagram_builder_class == NO_CHANGE ? @datagram_builder_class : datagram_builder_class,
|
|
628
641
|
enable_aggregation: @enable_aggregation,
|
|
629
642
|
aggregation_flush_interval: @aggregation_flush_interval,
|
|
643
|
+
aggregator: aggregator == NO_CHANGE ? @injected_aggregator : aggregator,
|
|
630
644
|
)
|
|
631
645
|
end
|
|
632
646
|
|
|
@@ -70,7 +70,8 @@ module StatsD
|
|
|
70
70
|
# Create a new class for this specific metric
|
|
71
71
|
# Using classes instead of instances for better YJIT optimization
|
|
72
72
|
metric_class = tap do
|
|
73
|
-
|
|
73
|
+
# Own a frozen name without freezing a mutable string from the caller.
|
|
74
|
+
@name = -DatagramBlueprintBuilder.normalize_name(name)
|
|
74
75
|
@datagram_blueprint = datagram_blueprint
|
|
75
76
|
@tag_combination_cache = {}
|
|
76
77
|
@max_cache_size = max_cache_size
|
|
@@ -300,9 +301,10 @@ module StatsD
|
|
|
300
301
|
|
|
301
302
|
# Normalizes metric names by replacing special characters
|
|
302
303
|
# @param name [String] The metric name
|
|
303
|
-
# @return [String] The
|
|
304
|
+
# @return [String] The original string if clean, otherwise a new string.
|
|
305
|
+
# Does not copy or freeze clean input; retaining callers own that step.
|
|
304
306
|
def normalize_name(name)
|
|
305
|
-
name
|
|
307
|
+
Sanitization.name(name)
|
|
306
308
|
end
|
|
307
309
|
|
|
308
310
|
private
|
|
@@ -314,16 +316,14 @@ module StatsD
|
|
|
314
316
|
def build_prefix(client_prefix, no_prefix)
|
|
315
317
|
return "" if no_prefix || client_prefix.nil?
|
|
316
318
|
|
|
317
|
-
"#{client_prefix}."
|
|
319
|
+
"#{Sanitization.name(client_prefix.to_s)}."
|
|
318
320
|
end
|
|
319
321
|
|
|
320
322
|
# Normalizes tag names/values by removing StatsD protocol special characters
|
|
321
323
|
# @param str [Symbol, String, Integer, Float] The string to normalize
|
|
322
324
|
# @return [String] The normalized string
|
|
323
325
|
def normalize_statsd_string(str)
|
|
324
|
-
str
|
|
325
|
-
str = str.tr("|,", "") if /[|,]/.match?(str)
|
|
326
|
-
str
|
|
326
|
+
Sanitization.tag(str.to_s)
|
|
327
327
|
end
|
|
328
328
|
|
|
329
329
|
# Compiles all tags (default_tags, static_tags, dynamic_tags) into a single string
|
|
@@ -412,10 +412,10 @@ module StatsD
|
|
|
412
412
|
# Sanitize string and symbol values (other types handled by sprintf %s)
|
|
413
413
|
values = @tag_values.map do |arg|
|
|
414
414
|
if arg.is_a?(String)
|
|
415
|
-
|
|
415
|
+
Sanitization::TAG_PATTERN.match?(arg) ? arg.tr(Sanitization::TAG_CHARACTERS, "") : arg
|
|
416
416
|
elsif arg.is_a?(Symbol)
|
|
417
417
|
str = arg.to_s
|
|
418
|
-
|
|
418
|
+
Sanitization::TAG_PATTERN.match?(str) ? str.tr(Sanitization::TAG_CHARACTERS, "") : str
|
|
419
419
|
else
|
|
420
420
|
arg
|
|
421
421
|
end
|
|
@@ -72,9 +72,9 @@ module StatsD
|
|
|
72
72
|
|
|
73
73
|
PARSER = %r{
|
|
74
74
|
\A
|
|
75
|
-
(?<name>[
|
|
76
|
-
(
|
|
77
|
-
(?:\|\#(?<tags>(?:[
|
|
75
|
+
(?<name>[^:|@]+):(?<value>(?:[^:|@]+:)*[^:|@]+)\|(?<type>c|ms|g|s|h|d)
|
|
76
|
+
(?:\|@(?<sample_rate>\d*(?:\.\d*)?))?
|
|
77
|
+
(?:\|\#(?<tags>(?:[^|,]+(?:,[^|,]+)*)))?
|
|
78
78
|
\n? # In some implementations, the datagram may include a trailing newline.
|
|
79
79
|
\z
|
|
80
80
|
}x.freeze
|
|
@@ -20,13 +20,13 @@ module StatsD
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def normalize_string(string)
|
|
23
|
-
string = string.tr("
|
|
23
|
+
string = string.tr("|#\r\n", "_") if /[|#\r\n]/.match?(string)
|
|
24
24
|
string
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def initialize(prefix: nil, default_tags: nil)
|
|
29
|
-
@prefix = prefix.nil? ? "" : "#{prefix}."
|
|
29
|
+
@prefix = prefix.nil? ? "" : "#{Sanitization.name(prefix.to_s)}."
|
|
30
30
|
@default_tags = default_tags.nil? || default_tags.empty? ? nil : compile_tags(default_tags, "|#".b)
|
|
31
31
|
end
|
|
32
32
|
|
|
@@ -43,6 +43,8 @@ module StatsD
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def s(name, value, sample_rate, tags)
|
|
46
|
+
value = value.to_s
|
|
47
|
+
value = value.tr("\r\n", "_") if /[\r\n]/.match?(value)
|
|
46
48
|
generate_generic_datagram(name, value, "s", sample_rate, tags)
|
|
47
49
|
end
|
|
48
50
|
|
|
@@ -76,16 +78,13 @@ module StatsD
|
|
|
76
78
|
|
|
77
79
|
# Utility function to remove invalid characters from a StatsD metric name
|
|
78
80
|
def normalize_name(name)
|
|
79
|
-
|
|
80
|
-
return name unless /[:|@]/.match?(name)
|
|
81
|
-
|
|
82
|
-
name.tr(":|@", "_")
|
|
81
|
+
Sanitization.name(name)
|
|
83
82
|
end
|
|
84
83
|
|
|
85
84
|
def generate_generic_datagram(name, value, type, sample_rate, tags)
|
|
86
85
|
datagram = "".b <<
|
|
87
86
|
@prefix <<
|
|
88
|
-
(
|
|
87
|
+
(Sanitization::NAME_PATTERN.match?(name) ? name.tr(Sanitization::NAME_CHARACTERS, "_") : name) <<
|
|
89
88
|
":" << value.to_s <<
|
|
90
89
|
"|" << type
|
|
91
90
|
|
|
@@ -105,7 +104,8 @@ module StatsD
|
|
|
105
104
|
|
|
106
105
|
def compile_tags(tags, buffer = "".b)
|
|
107
106
|
if tags.is_a?(String)
|
|
108
|
-
|
|
107
|
+
# String tags are already serialized: commas separate tags here.
|
|
108
|
+
tags = self.class.normalize_string(tags) if Sanitization::TAG_PATTERN.match?(tags)
|
|
109
109
|
buffer << tags
|
|
110
110
|
return buffer
|
|
111
111
|
end
|
|
@@ -118,14 +118,14 @@ module StatsD
|
|
|
118
118
|
buffer << ","
|
|
119
119
|
end
|
|
120
120
|
key = key.to_s
|
|
121
|
-
key = key.tr(
|
|
121
|
+
key = key.tr(Sanitization::TAG_CHARACTERS, "") if Sanitization::TAG_PATTERN.match?(key)
|
|
122
122
|
value = value.to_s
|
|
123
|
-
value = value.tr(
|
|
123
|
+
value = value.tr(Sanitization::TAG_CHARACTERS, "") if Sanitization::TAG_PATTERN.match?(value)
|
|
124
124
|
buffer << key << ":" << value
|
|
125
125
|
end
|
|
126
126
|
else
|
|
127
|
-
if tags.any? { |tag|
|
|
128
|
-
tags = tags.map { |tag| tag
|
|
127
|
+
if tags.any? { |tag| Sanitization::TAG_PATTERN.match?(tag) }
|
|
128
|
+
tags = tags.map { |tag| Sanitization.tag(tag) }
|
|
129
129
|
end
|
|
130
130
|
buffer << tags.join(",")
|
|
131
131
|
end
|
|
@@ -62,11 +62,11 @@ module StatsD
|
|
|
62
62
|
|
|
63
63
|
SERVICE_CHECK_PARSER = %r{
|
|
64
64
|
\A
|
|
65
|
-
(?<type>_sc)\|(?<name>[
|
|
66
|
-
(?:\|h:(?<hostname>[
|
|
65
|
+
(?<type>_sc)\|(?<name>[^|]+)\|(?<value>\d+)
|
|
66
|
+
(?:\|h:(?<hostname>[^|]+))?
|
|
67
67
|
(?:\|d:(?<timestamp>\d+))?
|
|
68
|
-
(?:\|\#(?<tags>(?:[
|
|
69
|
-
(?:\|m:(?<message>[
|
|
68
|
+
(?:\|\#(?<tags>(?:[^|,]+(?:,[^|,]+)*)))?
|
|
69
|
+
(?:\|m:(?<message>[^|]+))?
|
|
70
70
|
\n? # In some implementations, the datagram may include a trailing newline.
|
|
71
71
|
\z
|
|
72
72
|
}x.freeze
|
|
@@ -74,14 +74,14 @@ module StatsD
|
|
|
74
74
|
# |k:my-key|p:low|s:source|t:success|
|
|
75
75
|
EVENT_PARSER = %r{
|
|
76
76
|
\A
|
|
77
|
-
(?<type>_e)\{\d
|
|
78
|
-
(?:\|h:(?<hostname>[
|
|
77
|
+
(?<type>_e)\{\d+,\d+\}:(?<name>[^|]+)\|(?<value>[^|]+)
|
|
78
|
+
(?:\|h:(?<hostname>[^|]+))?
|
|
79
79
|
(?:\|d:(?<timestamp>\d+))?
|
|
80
|
-
(?:\|k:(?<aggregation_key>[
|
|
81
|
-
(?:\|p:(?<priority>[
|
|
82
|
-
(?:\|s:(?<source_type_name>[
|
|
83
|
-
(?:\|t:(?<alert_type>[
|
|
84
|
-
(?:\|\#(?<tags>(?:[
|
|
80
|
+
(?:\|k:(?<aggregation_key>[^|]+))?
|
|
81
|
+
(?:\|p:(?<priority>[^|]+))?
|
|
82
|
+
(?:\|s:(?<source_type_name>[^|]+))?
|
|
83
|
+
(?:\|t:(?<alert_type>[^|]+))?
|
|
84
|
+
(?:\|\#(?<tags>(?:[^|,]+(?:,[^|,]+)*)))?
|
|
85
85
|
\n? # In some implementations, the datagram may include a trailing newline.
|
|
86
86
|
\z
|
|
87
87
|
}x.freeze
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StatsD
|
|
4
|
+
module Instrument
|
|
5
|
+
# Field-specific wire normalization. Clean strings are returned unchanged;
|
|
6
|
+
# callers retaining a frozen value must take ownership themselves.
|
|
7
|
+
# @api private
|
|
8
|
+
module Sanitization
|
|
9
|
+
# Hot serializers use these same rules inline to avoid per-field Ruby calls.
|
|
10
|
+
NAME_PATTERN = /[:|@\s]/.freeze
|
|
11
|
+
NAME_CHARACTERS = ":|@ \t\r\n\f\v"
|
|
12
|
+
TAG_PATTERN = /[|,\r\n]/.freeze
|
|
13
|
+
TAG_CHARACTERS = "|,\r\n"
|
|
14
|
+
|
|
15
|
+
class << self
|
|
16
|
+
# Replace each ASCII whitespace or protocol delimiter with one underscore.
|
|
17
|
+
# @return [String] The original string if clean, otherwise a new string.
|
|
18
|
+
def name(string)
|
|
19
|
+
NAME_PATTERN.match?(string) ? string.tr(NAME_CHARACTERS, "_") : string
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Tag components allow spaces and colons, but not field/tag separators.
|
|
23
|
+
def tag(string)
|
|
24
|
+
TAG_PATTERN.match?(string) ? string.tr(TAG_CHARACTERS, "") : string
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Keep message whitespace intact; this is not a metric name.
|
|
28
|
+
def service_check_message(string)
|
|
29
|
+
/[:|@\r\n]/.match?(string) ? string.tr(":|@\r\n", "_") : string
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
data/lib/statsd/instrument.rb
CHANGED
|
@@ -391,6 +391,7 @@ require "statsd/instrument/client"
|
|
|
391
391
|
require "statsd/instrument/datagram"
|
|
392
392
|
require "statsd/instrument/aggregator"
|
|
393
393
|
require "statsd/instrument/dogstatsd_datagram"
|
|
394
|
+
require "statsd/instrument/sanitization"
|
|
394
395
|
require "statsd/instrument/datagram_builder"
|
|
395
396
|
require "statsd/instrument/statsd_datagram_builder"
|
|
396
397
|
require "statsd/instrument/dogstatsd_datagram_builder"
|