statsd-instrument 3.1.2 → 3.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 85b73b161dac9bc8839c3b820e9ea038d05078519fca692e971a48be7077e799
4
- data.tar.gz: e2d5bfa763a5d53537494c8a5006327a93967d4d2b63f74a685094c551815c81
3
+ metadata.gz: f42d327300430dd5926c971c7d27d860acd99800cc8eeb4552892f755037553f
4
+ data.tar.gz: 88c22b7ef591313d0e159a24f3875ddc7a63acf0c6d10551cac6bf069a6c0ad3
5
5
  SHA512:
6
- metadata.gz: e6db90f921635692d1d19145e3b346029886022c679649fac3e6f5641411ddc557fcd2028baff7be4e0b47a23e53f97e4afe9af4ecb7e537dc0850b32352fbec
7
- data.tar.gz: 6c8f12361a70596d49dc84b3feb6637bc0ff7e9d62f1e6e7876ec02111cc94a2b561947ac4bfda045f612f57951f9789e578447e67a0b8bda4beec4f0fc76576
6
+ metadata.gz: 8a0d04e0acf3ddb8ae85bb3d5b4a560dbb713c6c8d26654cca6d52b5b3d70a8570b424b489e33d5bcc301f419db8e1de448daaee863fb0624ff52aa10c67c9a4
7
+ data.tar.gz: f56d3bd3ecd117d9a359de5445f3b00750d11151a40da26f6e902cd1c5800c8688ae1bab05f2457eb519751c642f69278425ba4541a6fe6caa41dd766dd3a21c
@@ -5,18 +5,16 @@ on: push
5
5
  jobs:
6
6
  test:
7
7
  name: Send metric over UDP
8
- runs-on: ubuntu-18.04
8
+ runs-on: ubuntu-latest
9
9
 
10
10
  steps:
11
11
  - uses: actions/checkout@v1
12
12
 
13
- - name: Setup Ruby
14
- uses: actions/setup-ruby@v1
13
+ - name: Set up Ruby
14
+ uses: ruby/setup-ruby@v1
15
15
  with:
16
16
  ruby-version: 2.6
17
-
18
- - name: Install dependencies
19
- run: gem install bundler && bundle install --jobs 4 --retry 3
17
+ bundler-cache: true
20
18
 
21
19
  - name: Run benchmark on branch
22
20
  run: benchmark/send-metrics-to-local-udp-receiver
@@ -5,18 +5,16 @@ on: push
5
5
  jobs:
6
6
  test:
7
7
  name: Rubocop
8
- runs-on: ubuntu-18.04
8
+ runs-on: ubuntu-latest
9
9
 
10
10
  steps:
11
11
  - uses: actions/checkout@v1
12
12
 
13
- - name: Setup Ruby
14
- uses: actions/setup-ruby@v1
13
+ - name: Set up Ruby
14
+ uses: ruby/setup-ruby@v1
15
15
  with:
16
- ruby-version: 2.7
17
-
18
- - name: Install dependencies
19
- run: gem install bundler && bundle install --jobs 4 --retry 3
16
+ ruby-version: 2.6
17
+ bundler-cache: true
20
18
 
21
19
  - name: Run Rubocop
22
20
  run: bin/rubocop
@@ -4,12 +4,12 @@ on: push
4
4
 
5
5
  jobs:
6
6
  test:
7
- name: Ruby ${{ matrix.ruby }} on ubuntu-18.04
8
- runs-on: ubuntu-18.04
7
+ name: Ruby ${{ matrix.ruby }} on ubuntu-latest
8
+ runs-on: ubuntu-latest
9
9
  strategy:
10
10
  fail-fast: false
11
11
  matrix:
12
- ruby: ['2.6', '2.7', '3.0']
12
+ ruby: ['2.6', '2.7', '3.0', '3.1']
13
13
 
14
14
  # Windows on macOS builds started failing, so they are disabled for noew
15
15
  # platform: [windows-2019, macOS-10.14, ubuntu-18.04]
@@ -19,13 +19,11 @@ jobs:
19
19
  steps:
20
20
  - uses: actions/checkout@v1
21
21
 
22
- - name: Setup Ruby
23
- uses: actions/setup-ruby@v1
22
+ - name: Set up Ruby
23
+ uses: ruby/setup-ruby@v1
24
24
  with:
25
25
  ruby-version: ${{ matrix.ruby }}
26
-
27
- - name: Install dependencies
28
- run: gem install bundler && bundle install --jobs 4 --retry 3
26
+ bundler-cache: true
29
27
 
30
28
  - name: Run test suite
31
- run: rake test
29
+ run: bundle exec rake test
data/CHANGELOG.md CHANGED
@@ -8,6 +8,10 @@ section below.
8
8
 
9
9
  _Nothing yet_
10
10
 
11
+ ## Version 3.2.0
12
+
13
+ - Add `tag_error_class` option to `statsd_count_success` which tags the class of a thrown error
14
+
11
15
  ## Version 3.1.2
12
16
 
13
17
  - Fix bug when passing custom client to expectation.
data/Gemfile CHANGED
@@ -8,6 +8,6 @@ gem "minitest"
8
8
  gem "rspec"
9
9
  gem "mocha"
10
10
  gem "yard"
11
- gem "rubocop", ">= 1.0"
11
+ gem "rubocop", [">= 1.0", "< 1.30"] # TODO: Our cops are broken by rubocop 1.30, we need to figure out why
12
12
  gem "rubocop-shopify", require: false
13
13
  gem "benchmark-ips"
data/README.md CHANGED
@@ -27,7 +27,7 @@ The following environment variables are supported:
27
27
  explicitly, this will be determined based on other environment variables,
28
28
  like `RAILS_ENV` or `ENV`. The library will behave differently:
29
29
 
30
- - In the **production** and **staging** environment, thre library will
30
+ - In the **production** and **staging** environment, the library will
31
31
  actually send UDP packets.
32
32
  - In the **test** environment, it will swallow all calls, but allows you to
33
33
  capture them for testing purposes. See below for notes on writing tests.
@@ -57,6 +57,7 @@ module StatsD
57
57
  def assert_no_statsd_calls(*metric_names, datagrams: nil, client: nil, &block)
58
58
  if datagrams.nil?
59
59
  raise LocalJumpError, "assert_no_statsd_calls requires a block" unless block_given?
60
+
60
61
  datagrams = capture_statsd_datagrams_with_exception_handling(client: client, &block)
61
62
  end
62
63
 
@@ -152,6 +153,7 @@ module StatsD
152
153
  def assert_statsd_expectations(expectations, datagrams: nil, client: nil, &block)
153
154
  if datagrams.nil?
154
155
  raise LocalJumpError, "assert_statsd_expectations requires a block" unless block_given?
156
+
155
157
  datagrams = capture_statsd_datagrams_with_exception_handling(client: client, &block)
156
158
  end
157
159
 
@@ -200,6 +200,7 @@ module StatsD
200
200
  def increment(name, value = 1, sample_rate: nil, tags: nil, no_prefix: false)
201
201
  sample_rate ||= @default_sample_rate
202
202
  return StatsD::Instrument::VOID unless sample?(sample_rate)
203
+
203
204
  emit(datagram_builder(no_prefix: no_prefix).c(name, value, sample_rate, tags))
204
205
  end
205
206
 
@@ -217,6 +218,7 @@ module StatsD
217
218
 
218
219
  sample_rate ||= @default_sample_rate
219
220
  return StatsD::Instrument::VOID unless sample?(sample_rate)
221
+
220
222
  emit(datagram_builder(no_prefix: no_prefix).ms(name, value, sample_rate, tags))
221
223
  end
222
224
 
@@ -236,6 +238,7 @@ module StatsD
236
238
  def gauge(name, value, sample_rate: nil, tags: nil, no_prefix: false)
237
239
  sample_rate ||= @default_sample_rate
238
240
  return StatsD::Instrument::VOID unless sample?(sample_rate)
241
+
239
242
  emit(datagram_builder(no_prefix: no_prefix).g(name, value, sample_rate, tags))
240
243
  end
241
244
 
@@ -249,6 +252,7 @@ module StatsD
249
252
  def set(name, value, sample_rate: nil, tags: nil, no_prefix: false)
250
253
  sample_rate ||= @default_sample_rate
251
254
  return StatsD::Instrument::VOID unless sample?(sample_rate)
255
+
252
256
  emit(datagram_builder(no_prefix: no_prefix).s(name, value, sample_rate, tags))
253
257
  end
254
258
 
@@ -271,6 +275,7 @@ module StatsD
271
275
 
272
276
  sample_rate ||= @default_sample_rate
273
277
  return StatsD::Instrument::VOID unless sample?(sample_rate)
278
+
274
279
  emit(datagram_builder(no_prefix: no_prefix).d(name, value, sample_rate, tags))
275
280
  end
276
281
 
@@ -288,6 +293,7 @@ module StatsD
288
293
  def histogram(name, value, sample_rate: nil, tags: nil, no_prefix: false)
289
294
  sample_rate ||= @default_sample_rate
290
295
  return StatsD::Instrument::VOID unless sample?(sample_rate)
296
+
291
297
  emit(datagram_builder(no_prefix: no_prefix).h(name, value, sample_rate, tags))
292
298
  end
293
299
 
@@ -67,10 +67,12 @@ module StatsD
67
67
  # @return [Array<String>, nil] the list of tags in canonical form.
68
68
  def normalize_tags(tags)
69
69
  return [] unless tags
70
+
70
71
  tags = tags.map { |k, v| "#{k}:#{v}" } if tags.is_a?(Hash)
71
72
 
72
73
  # Fast path when no string replacement is needed
73
74
  return tags unless tags.any? { |tag| /[|,]/.match?(tag) }
75
+
74
76
  tags.map { |tag| tag.tr("|,", "") }
75
77
  end
76
78
 
@@ -78,6 +80,7 @@ module StatsD
78
80
  def normalize_name(name)
79
81
  # Fast path when no normalization is needed to avoid copying the string
80
82
  return name unless /[:|@]/.match?(name)
83
+
81
84
  name.tr(":|@", "_")
82
85
  end
83
86
 
@@ -93,10 +93,12 @@ module StatsD
93
93
  # to ensure that this logic matches the logic of the active datagram builder.
94
94
  def normalize_tags(tags)
95
95
  return [] unless tags
96
+
96
97
  tags = tags.map { |k, v| "#{k}:#{v}" } if tags.is_a?(Hash)
97
98
 
98
99
  # Fast path when no string replacement is needed
99
100
  return tags unless tags.any? { |tag| /[|,]/.match?(tag) }
101
+
100
102
  tags.map { |tag| tag.tr("|,", "") }
101
103
  end
102
104
  end
@@ -10,6 +10,22 @@ module StatsD
10
10
 
11
11
  # For backwards compatibility
12
12
  alias_method :capture_statsd_calls, :capture_statsd_datagrams
13
+
14
+ def self.add_tag(tags, key, value)
15
+ tags = tags.dup || {}
16
+
17
+ if tags.is_a?(String)
18
+ tags = tags.empty? ? "#{key}:#{value}" : "#{tags},#{key}:#{value}"
19
+ elsif tags.is_a?(Array)
20
+ tags << "#{key}:#{value}"
21
+ elsif tags.is_a?(Hash)
22
+ tags[key] = value
23
+ else
24
+ raise ArgumentError, "add_tag only supports string, array or hash, #{tags.class} provided"
25
+ end
26
+
27
+ tags
28
+ end
13
29
  end
14
30
  end
15
31
  end
@@ -49,7 +49,7 @@ module StatsD
49
49
  raise RSpec::Expectations::ExpectationNotMetError, "No StatsD calls for metric #{metric_name} were made."
50
50
  elsif options[:times] && options[:times] != metrics.length
51
51
  raise RSpec::Expectations::ExpectationNotMetError, "The numbers of StatsD calls for metric " \
52
- "#{metric_name} was unexpected. Expected #{options[:times].inspect}, got #{metrics.length}"
52
+ "#{metric_name} was unexpected. Expected #{options[:times].inspect}, got #{metrics.length}"
53
53
  end
54
54
 
55
55
  [:sample_rate, :value, :tags].each do |expectation|
@@ -41,7 +41,7 @@ module RuboCop
41
41
  end
42
42
 
43
43
  def autocorrect(node)
44
- -> (corrector) do
44
+ ->(corrector) do
45
45
  positional_arguments = if node.arguments.last.type == :block_pass
46
46
  node.arguments[2...node.arguments.length - 1]
47
47
  else
@@ -39,6 +39,7 @@ module RuboCop
39
39
 
40
40
  def keyword_arguments(node)
41
41
  return nil if node.arguments.empty?
42
+
42
43
  last_argument = if node.arguments.last&.type == :block_pass
43
44
  node.arguments[node.arguments.length - 2]
44
45
  else
@@ -11,6 +11,7 @@ module StatsD
11
11
 
12
12
  def normalize_tags(tags)
13
13
  raise NotImplementedError, "#{self.class.name} does not support tags" if tags
14
+
14
15
  super
15
16
  end
16
17
  end
@@ -27,6 +27,7 @@ module StatsD
27
27
  def increment(key, value = 1, sample_rate: nil, tags: nil, no_prefix: false)
28
28
  raise ArgumentError, "StatsD.increment does not accept a block" if block_given?
29
29
  raise ArgumentError, "The value argument should be an integer, got #{value.inspect}" unless value.is_a?(Integer)
30
+
30
31
  check_tags_and_sample_rate(sample_rate, tags)
31
32
 
32
33
  super
@@ -35,6 +36,7 @@ module StatsD
35
36
  def gauge(key, value, sample_rate: nil, tags: nil, no_prefix: false)
36
37
  raise ArgumentError, "StatsD.increment does not accept a block" if block_given?
37
38
  raise ArgumentError, "The value argument should be an integer, got #{value.inspect}" unless value.is_a?(Numeric)
39
+
38
40
  check_tags_and_sample_rate(sample_rate, tags)
39
41
 
40
42
  super
@@ -43,6 +45,7 @@ module StatsD
43
45
  def histogram(key, value, sample_rate: nil, tags: nil, no_prefix: false)
44
46
  raise ArgumentError, "StatsD.increment does not accept a block" if block_given?
45
47
  raise ArgumentError, "The value argument should be an integer, got #{value.inspect}" unless value.is_a?(Numeric)
48
+
46
49
  check_tags_and_sample_rate(sample_rate, tags)
47
50
 
48
51
  super
@@ -50,6 +53,7 @@ module StatsD
50
53
 
51
54
  def set(key, value, sample_rate: nil, tags: nil, no_prefix: false)
52
55
  raise ArgumentError, "StatsD.set does not accept a block" if block_given?
56
+
53
57
  check_tags_and_sample_rate(sample_rate, tags)
54
58
 
55
59
  super
@@ -26,13 +26,11 @@ module StatsD
26
26
  def <<(datagram)
27
27
  with_socket { |socket| socket.send(datagram, 0) }
28
28
  self
29
-
30
29
  rescue ThreadError
31
30
  # In cases where a TERM or KILL signal has been sent, and we send stats as
32
31
  # part of a signal handler, locks cannot be acquired, so we do our best
33
32
  # to try and send the datagram without a lock.
34
33
  socket.send(datagram, 0) > 0
35
-
36
34
  rescue SocketError, IOError, SystemCallError => error
37
35
  StatsD.logger.debug do
38
36
  "[StatsD::Instrument::UDPSink] Resetting connection because of #{error.class}: #{error.message}"
@@ -2,6 +2,6 @@
2
2
 
3
3
  module StatsD
4
4
  module Instrument
5
- VERSION = "3.1.2"
5
+ VERSION = "3.2.0"
6
6
  end
7
7
  end
@@ -104,24 +104,26 @@ module StatsD
104
104
  # @param method (see #statsd_measure)
105
105
  # @param name (see #statsd_measure)
106
106
  # @param metric_options (see #statsd_measure)
107
+ # @param tag_error_class add a <tt>error_class</tt> tag with the error class when an error is thrown
107
108
  # @yield You can pass a block to this method if you want to define yourself what is a successful call
108
109
  # based on the return value of the method.
109
110
  # @yieldparam result The return value of the instrumented method.
110
111
  # @yieldreturn [Boolean] Return true iff the return value is considered a success, false otherwise.
111
112
  # @return [void]
112
113
  # @see #statsd_count_if
113
- def statsd_count_success(method, name, sample_rate: nil, tags: nil, no_prefix: false, client: nil)
114
+ def statsd_count_success(method, name, sample_rate: nil,
115
+ tags: nil, no_prefix: false, client: nil, tag_error_class: false)
114
116
  add_to_method(method, name, :count_success) do
115
117
  define_method(method) do |*args, &block|
116
118
  truthiness = result = super(*args, &block)
117
- rescue
119
+ rescue => error
118
120
  truthiness = false
119
121
  raise
120
122
  else
121
123
  if block_given?
122
124
  begin
123
125
  truthiness = yield(result)
124
- rescue
126
+ rescue => error
125
127
  truthiness = false
126
128
  end
127
129
  end
@@ -130,6 +132,9 @@ module StatsD
130
132
  client ||= StatsD.singleton_client
131
133
  suffix = truthiness == false ? "failure" : "success"
132
134
  key = StatsD::Instrument.generate_metric_name(name, self, *args)
135
+
136
+ tags = Helpers.add_tag(tags, :error_class, error.class.name) if tag_error_class && error
137
+
133
138
  client.increment("#{key}.#{suffix}", sample_rate: sample_rate, tags: tags, no_prefix: no_prefix)
134
139
  end
135
140
  end
data/test/helpers_test.rb CHANGED
@@ -35,8 +35,47 @@ class HelpersTest < Minitest::Test
35
35
  StatsD.gauge("gauge", 15)
36
36
 
37
37
  assert_equal(2, metrics.length)
38
-
39
38
  ensure
40
39
  StatsD.singleton_client = @old_client
41
40
  end
41
+
42
+ def test_add_tag_works_for_nil
43
+ assert_equal({ key: 123 }, StatsD::Instrument::Helpers.add_tag(nil, :key, 123))
44
+ end
45
+
46
+ def test_add_tag_works_for_hashes
47
+ assert_equal({ key: 123 }, StatsD::Instrument::Helpers.add_tag({}, :key, 123))
48
+
49
+ existing = { existing: 123 }
50
+ assert_equal({ existing: 123, new: 456 }, StatsD::Instrument::Helpers.add_tag(existing, :new, 456))
51
+
52
+ # ensure we do not modify the existing tags
53
+ assert_equal({ existing: 123 }, existing)
54
+ end
55
+
56
+ def test_add_tag_works_for_arrays
57
+ assert_equal(["key:123"], StatsD::Instrument::Helpers.add_tag([], :key, 123))
58
+
59
+ existing = ["existing:123"]
60
+ assert_equal(["existing:123", "new:456"], StatsD::Instrument::Helpers.add_tag(existing, :new, 456))
61
+
62
+ # ensure we do not modify the existing tags
63
+ assert_equal(["existing:123"], existing)
64
+ end
65
+
66
+ def test_add_tag_works_for_strings
67
+ assert_equal("key:123", StatsD::Instrument::Helpers.add_tag("", :key, 123))
68
+
69
+ existing = "existing:123"
70
+ assert_equal("existing:123,new:456", StatsD::Instrument::Helpers.add_tag(existing, :new, 456))
71
+
72
+ # ensure we do not modify the existing tags
73
+ assert_equal("existing:123", existing)
74
+ end
75
+
76
+ def test_add_tags_raises_for_other
77
+ assert_raises(ArgumentError, "add_tag only supports string, array or hash, Integer provided") do
78
+ StatsD::Instrument::Helpers.add_tag(1, :key, 123)
79
+ end
80
+ end
42
81
  end
@@ -159,6 +159,38 @@ class StatsDInstrumentationTest < Minitest::Test
159
159
  ActiveMerchant::UniqueGateway.statsd_remove_count_success(:ssl_post, "ActiveMerchant.Gateway")
160
160
  end
161
161
 
162
+ def test_statsd_count_success_tag_error_class
163
+ ActiveMerchant::Base.statsd_count_success(:ssl_post, "ActiveMerchant.Base", tag_error_class: true)
164
+
165
+ assert_statsd_increment("ActiveMerchant.Base.success", tags: nil) do
166
+ ActiveMerchant::Base.new.ssl_post(true)
167
+ end
168
+
169
+ assert_statsd_increment("ActiveMerchant.Base.failure", tags: ["error_class:RuntimeError"]) do
170
+ assert_raises(RuntimeError, "Not OK") do
171
+ ActiveMerchant::Base.new.ssl_post(false)
172
+ end
173
+ end
174
+ ensure
175
+ ActiveMerchant::Base.statsd_remove_count_success(:ssl_post, "ActiveMerchant.Base")
176
+ end
177
+
178
+ def test_statsd_count_success_tag_error_class_is_opt_in
179
+ ActiveMerchant::Base.statsd_count_success(:ssl_post, "ActiveMerchant.Base")
180
+
181
+ assert_statsd_increment("ActiveMerchant.Base.success", tags: nil) do
182
+ ActiveMerchant::Base.new.ssl_post(true)
183
+ end
184
+
185
+ assert_statsd_increment("ActiveMerchant.Base.failure", tags: nil) do
186
+ assert_raises(RuntimeError, "Not OK") do
187
+ ActiveMerchant::Base.new.ssl_post(false)
188
+ end
189
+ end
190
+ ensure
191
+ ActiveMerchant::Base.statsd_remove_count_success(:ssl_post, "ActiveMerchant.Base")
192
+ end
193
+
162
194
  def test_statsd_count
163
195
  ActiveMerchant::Gateway.statsd_count(:ssl_post, "ActiveMerchant.Gateway.ssl_post")
164
196
 
@@ -136,7 +136,7 @@ module UDPSinkTests
136
136
 
137
137
  assert_equal(
138
138
  "[#{@sink_class}] Resetting connection because of " \
139
- "Errno::EDESTADDRREQ: Destination address required\n",
139
+ "Errno::EDESTADDRREQ: Destination address required\n",
140
140
  logs.string,
141
141
  )
142
142
  ensure
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: statsd-instrument
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.2
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jesse Storimer
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2021-09-02 00:00:00.000000000 Z
13
+ date: 2022-06-22 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: concurrent-ruby
@@ -134,7 +134,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
134
134
  - !ruby/object:Gem::Version
135
135
  version: '0'
136
136
  requirements: []
137
- rubygems_version: 3.2.20
137
+ rubygems_version: 3.3.3
138
138
  signing_key:
139
139
  specification_version: 4
140
140
  summary: A StatsD client for Ruby apps