statsd-instrument 3.1.2 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
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