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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 51853f9d4b70fc39649e251e7e93ff3d77b2bcca1d776182f82de8219ea86886
4
- data.tar.gz: fce22a816682f0bc25510b6a68248a64c93ba983e7640b57ec01dbb0b985c9d2
3
+ metadata.gz: c7184b3fb0852d7afea02fa71a292812ef26b491eb94ab2427f603b49f01202c
4
+ data.tar.gz: e40e7aa3d4cb034467b6d59fdfdb3bcbf55a2901e82c48368fd1e904d0aa67c5
5
5
  SHA512:
6
- metadata.gz: 4a06bd60427001260e206243f4db74ecb3d8225c1c5e3f8f9764239bc40901457cf621d7f5fbd0936d39fa0765145499672a9fb0a9c22025ac8611f7051f7154
7
- data.tar.gz: f74279e49141844c564d896e1d04b88b6b9f66585e56401a4ee52f7f4cb8dcc5fbe6a99b8427fbfa45ca1be843fef5a8c9357fb629129372880ac5020aceab31
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 = 1, tags: [], no_prefix: false, sample_rate: CONST_SAMPLE_RATE)
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: [], no_prefix: false, type: DISTRIBUTION, sample_rate: CONST_SAMPLE_RATE)
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: [], no_prefix: false)
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
- @enable_aggregation = enable_aggregation
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 the following characters: `|`, `@`, and `:`.
200
- # The library will convert these characters to `_`.
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: tags, no_prefix: no_prefix, sample_rate: sample_rate)
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: tags, no_prefix: no_prefix, type: :ms, sample_rate: sample_rate)
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: tags, no_prefix: no_prefix)
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: tags, no_prefix: no_prefix, type: :h, sample_rate: sample_rate)
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: tags,
512
- no_prefix: no_prefix,
513
- type: metric_type,
514
- sample_rate: 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
- @name = DatagramBlueprintBuilder.normalize_name(name).freeze
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 normalized metric name
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.tr(":|@", "_")
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 = str.to_s
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
- /[|,]/.match?(arg) ? arg.tr("|,", "") : arg
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
- /[|,]/.match?(str) ? str.tr("|,", "") : str
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>[^\:\|\@]+)\:(?<value>(?:[^\:\|\@]+:)*[^\:\|\@]+)\|(?<type>c|ms|g|s|h|d)
76
- (?:\|\@(?<sample_rate>\d*(?:\.\d*)?))?
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("|#", "_") if /[|#]/.match?(string)
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}.".tr(":|@", "_")
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
- # Fast path when no normalization is needed to avoid copying the string
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
- (/[:|@]/.match?(name) ? name.tr(":|@", "_") : name) <<
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
- tags = self.class.normalize_string(tags) if /[|,]/.match?(tags)
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("|,", "") if /[|,]/.match?(key)
121
+ key = key.tr(Sanitization::TAG_CHARACTERS, "") if Sanitization::TAG_PATTERN.match?(key)
122
122
  value = value.to_s
123
- value = value.tr("|,", "") if /[|,]/.match?(value)
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| /[|,]/.match?(tag) }
128
- tags = tags.map { |tag| tag.tr("|,", "") }
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>[^\|]+)\|(?<value>\d+)
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+\,\d+\}:(?<name>[^\|]+)\|(?<value>[^\|]+)
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
@@ -84,7 +84,7 @@ module StatsD
84
84
  compile_tags(tags, datagram)
85
85
  end
86
86
 
87
- datagram << "|m:#{normalize_name(message)}" if message
87
+ datagram << "|m:#{Sanitization.service_check_message(message)}" if message
88
88
  datagram
89
89
  end
90
90
 
@@ -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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module StatsD
4
4
  module Instrument
5
- VERSION = "3.11.2"
5
+ VERSION = "4.0.1"
6
6
  end
7
7
  end
@@ -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"