dogstatsd-ruby 3.3.0 → 4.5.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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +34 -5
  3. data/lib/datadog/statsd.rb +290 -182
  4. metadata +4 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 046a0af9f230451c91fdeb9ae128c550b1c20623fa017d326e63a2f053f7cc9a
4
- data.tar.gz: e2ba838497de521e89d386645a0228f819fdd03c28acc05a4789aa7f8d979de8
3
+ metadata.gz: fde2be6b614574c2555c8cbbd2ca16ef33bcd09837502f482f5f6baf582fd67b
4
+ data.tar.gz: cc1523c5aa78643d93fc891116c46bb5322dda297f1676cd73d201c9beee2a26
5
5
  SHA512:
6
- metadata.gz: f2048376477bd1af31a63b75d17638df999c9f6341f967b2c826177add2a13831a54a72729e2a5c62aa09fa4aca6dccbaba308c79d9adcd470808d3bb80cde59
7
- data.tar.gz: 2e0ac5fc90f8a2359f35a754034bb6474c1169ac8c8f3301257555553e1aca4f708cecd4d9245ff835cbe4396561a89d3bd3700ef6cea6c92c343a1e41ca2055
6
+ metadata.gz: 9c96177f1ea0ab2288712cf9829cb8408687e12ce0f22f06ddbb1433447a3a4eaf05fe13f0fda8746b9f0e8fad71ea3b0b50ba7d8a028e464db334349951c64b
7
+ data.tar.gz: 11e23d434a7e88925e049484dfbb252d5c78ce12a4a07fd3c09edca57835db30224cba202f86f57352357acdf2073b8c8e93f20bd403975242b201321bef8ecc
data/README.md CHANGED
@@ -4,7 +4,7 @@ dogstatsd-ruby
4
4
 
5
5
  A client for DogStatsD, an extension of the StatsD metric server for Datadog.
6
6
 
7
- [![Build Status](https://secure.travis-ci.org/DataDog/dogstatsd-ruby.png)](http://travis-ci.org/DataDog/dogstatsd-ruby)
7
+ [![Build Status](https://secure.travis-ci.org/DataDog/dogstatsd-ruby.svg)](http://travis-ci.org/DataDog/dogstatsd-ruby)
8
8
 
9
9
  Quick Start Guide
10
10
  -----------------
@@ -28,9 +28,10 @@ statsd = Datadog::Statsd.new('localhost', 8125)
28
28
 
29
29
  # Increment a counter.
30
30
  statsd.increment('page.views')
31
+ statsd.increment('messages.count', by: 2, tags: ['kind:incoming'])
31
32
 
32
33
  # Record a gauge 50% of the time.
33
- statsd.gauge('users.online', 123, :sample_rate=>0.5)
34
+ statsd.gauge('users.online', 123, sample_rate: 0.5)
34
35
 
35
36
  # Sample a histogram
36
37
  statsd.histogram('file.upload.size', 1234)
@@ -48,7 +49,15 @@ statsd.batch do |s|
48
49
  end
49
50
 
50
51
  # Tag a metric.
51
- statsd.histogram('query.time', 10, :tags => ["version:1"])
52
+ statsd.histogram('query.time', 10, tags: ['version:1'])
53
+
54
+ # Tag a metric by passing in a Hash
55
+ statsd.histogram('query.time', 10, :tags => {version: 1})
56
+
57
+ # Auto-close socket after end of block
58
+ Datadog::Statsd.open('localhost', 8125) do |s|
59
+ s.increment('page.views')
60
+ end
52
61
  ```
53
62
 
54
63
  You can also post events to your stream. You can tag them, set priority and even aggregate them with other events.
@@ -57,14 +66,34 @@ Aggregation in the stream is made on hostname/event_type/source_type/aggregation
57
66
 
58
67
  ``` ruby
59
68
  # Post a simple message
60
- statsd.event("There might be a storm tomorrow", "A friend warned me earlier.")
69
+ statsd.event('There might be a storm tomorrow', 'A friend warned me earlier.')
61
70
 
62
71
  # Cry for help
63
- statsd.event("SO MUCH SNOW", "Started yesterday and it won't stop !!", :alert_type => "error", :tags => ["urgent", "endoftheworld"])
72
+ statsd.event(
73
+ 'SO MUCH SNOW',
74
+ "Started yesterday and it won't stop !!",
75
+ alert_type: 'error',
76
+ tags: ['urgent', 'endoftheworld']
77
+ )
64
78
  ```
65
79
 
66
80
 
67
81
 
82
+ Origin detection over UDP
83
+ -------------
84
+ Origin detection is a method to detect which pod DogStatsD packets are coming from in order to add the pod's tags to the tag list.
85
+
86
+ To enable origin detection over UDP, add the following lines to your application manifest
87
+ ```yaml
88
+ env:
89
+ - name: DD_ENTITY_ID
90
+ valueFrom:
91
+ fieldRef:
92
+ fieldPath: metadata.uid
93
+ ```
94
+ The DogStatsD client attaches an internal tag, `entity_id`. The value of this tag is the content of the `DD_ENTITY_ID` environment variable, which is the pod’s UID.
95
+
96
+
68
97
  Documentation
69
98
  -------------
70
99
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'socket'
2
3
 
3
4
  # = Datadog::Statsd: A DogStatsd client (https://www.datadoghq.com)
@@ -15,12 +16,151 @@ require 'socket'
15
16
  # statsd = Datadog::Statsd.new 'localhost', 8125, :namespace => 'account'
16
17
  # statsd.increment 'activate'
17
18
  # @example Create a statsd client with global tags
18
- # statsd = Datadog::Statsd.new 'localhost', 8125, :tags => 'tag1:true'
19
+ # statsd = Datadog::Statsd.new 'localhost', 8125, tags: 'tag1:true'
19
20
  module Datadog
20
21
  class Statsd
21
22
 
22
- DEFAULT_HOST = '127.0.0.1'
23
- DEFAULT_PORT = 8125
23
+ class Connection
24
+ DEFAULT_HOST = '127.0.0.1'
25
+ DEFAULT_PORT = 8125
26
+
27
+ # StatsD host. Defaults to 127.0.0.1.
28
+ attr_reader :host
29
+
30
+ # StatsD port. Defaults to 8125.
31
+ attr_reader :port
32
+
33
+ # DogStatsd unix socket path. Not used by default.
34
+ attr_reader :socket_path
35
+
36
+ # Close the underlying socket
37
+ def close
38
+ @socket && @socket.close
39
+ end
40
+
41
+ def write(message)
42
+ @logger.debug { "Statsd: #{message}" } if @logger
43
+ send_message(message)
44
+ rescue StandardError => boom
45
+ # Try once to reconnect if the socket has been closed
46
+ retries ||= 1
47
+ if retries <= 1 &&
48
+ (boom.is_a?(Errno::ENOTCONN) or
49
+ boom.is_a?(Errno::ECONNREFUSED) or
50
+ boom.is_a?(IOError) && boom.message =~ /closed stream/i)
51
+ retries += 1
52
+ begin
53
+ @socket = connect
54
+ retry
55
+ rescue StandardError => e
56
+ boom = e
57
+ end
58
+ end
59
+
60
+ @logger.error { "Statsd: #{boom.class} #{boom}" } if @logger
61
+ nil
62
+ end
63
+
64
+ private
65
+
66
+ def socket
67
+ @socket ||= connect
68
+ end
69
+ end
70
+
71
+ class UDPConnection < Connection
72
+ def initialize(host, port, logger)
73
+ @host = host || ENV.fetch('DD_AGENT_HOST', nil) || DEFAULT_HOST
74
+ @port = port || ENV.fetch('DD_DOGSTATSD_PORT', nil) || DEFAULT_PORT
75
+ @logger = logger
76
+ end
77
+
78
+ private
79
+
80
+ def connect
81
+ socket = UDPSocket.new
82
+ socket.connect(@host, @port)
83
+ socket
84
+ end
85
+
86
+ def send_message(message)
87
+ socket.send(message, 0)
88
+ end
89
+ end
90
+
91
+ class UDSConnection < Connection
92
+ class BadSocketError < StandardError; end
93
+
94
+ def initialize(socket_path, logger)
95
+ @socket_path = socket_path
96
+ @logger = logger
97
+ end
98
+
99
+ private
100
+
101
+ def connect
102
+ socket = Socket.new(Socket::AF_UNIX, Socket::SOCK_DGRAM)
103
+ socket.connect(Socket.pack_sockaddr_un(@socket_path))
104
+ socket
105
+ end
106
+
107
+ def send_message(message)
108
+ socket.sendmsg_nonblock(message)
109
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ENOENT => e
110
+ @socket = nil
111
+ raise BadSocketError, "#{e.class}: #{e}"
112
+ end
113
+ end
114
+
115
+ class Batch
116
+ def initialize(connection, max_buffer_bytes)
117
+ @connection = connection
118
+ @max_buffer_bytes = max_buffer_bytes
119
+ @depth = 0
120
+ reset
121
+ end
122
+
123
+ def open
124
+ @depth += 1
125
+ yield
126
+ ensure
127
+ @depth -= 1
128
+ flush if !open?
129
+ end
130
+
131
+ def open?
132
+ @depth > 0
133
+ end
134
+
135
+ def add(message)
136
+ message_bytes = message.bytesize
137
+
138
+ unless @buffer_bytes == 0
139
+ if @buffer_bytes + 1 + message_bytes >= @max_buffer_bytes
140
+ flush
141
+ else
142
+ @buffer << NEW_LINE
143
+ @buffer_bytes += 1
144
+ end
145
+ end
146
+
147
+ @buffer << message
148
+ @buffer_bytes += message_bytes
149
+ end
150
+
151
+ def flush
152
+ return if @buffer_bytes == 0
153
+ @connection.write @buffer
154
+ reset
155
+ end
156
+
157
+ private
158
+
159
+ def reset
160
+ @buffer = String.new
161
+ @buffer_bytes = 0
162
+ end
163
+ end
24
164
 
25
165
  # Create a dictionary to assign a key to every parameter's name, except for tags (treated differently)
26
166
  # Goal: Simple and fast to add some other parameters
@@ -46,78 +186,84 @@ module Datadog
46
186
  CRITICAL = 2
47
187
  UNKNOWN = 3
48
188
 
189
+ MAX_EVENT_SIZE = 8 * 1024
190
+
49
191
  COUNTER_TYPE = 'c'.freeze
50
192
  GAUGE_TYPE = 'g'.freeze
51
193
  HISTOGRAM_TYPE = 'h'.freeze
52
194
  DISTRIBUTION_TYPE = 'd'.freeze
53
195
  TIMING_TYPE = 'ms'.freeze
54
196
  SET_TYPE = 's'.freeze
55
- VERSION = "3.3.0".freeze
197
+ VERSION = "4.5.0".freeze
56
198
 
57
199
  # A namespace to prepend to all statsd calls. Defaults to no namespace.
58
200
  attr_reader :namespace
59
201
 
60
- # StatsD host. Defaults to 127.0.0.1.
61
- attr_reader :host
62
-
63
- # StatsD port. Defaults to 8125.
64
- attr_reader :port
65
-
66
- # DogStatsd unix socket path. Not used by default.
67
- attr_reader :socket_path
68
-
69
202
  # Global tags to be added to every statsd call. Defaults to no tags.
70
203
  attr_reader :tags
71
204
 
72
205
  # Buffer containing the statsd message before they are sent in batch
73
206
  attr_reader :buffer
74
207
 
75
- # Maximum number of metrics in the buffer before it is flushed
76
- attr_accessor :max_buffer_size
208
+ # Maximum buffer size in bytes before it is flushed
209
+ attr_reader :max_buffer_bytes
77
210
 
78
- class << self
79
- # Set to a standard logger instance to enable debug logging.
80
- attr_accessor :logger
81
- end
211
+ # Default sample rate
212
+ attr_reader :sample_rate
82
213
 
83
- # Return the current version of the library.
84
- # deprecated, but cannot be removed since uses might use it to check the version against older releases
85
- def self.VERSION
86
- VERSION
87
- end
214
+ # Connection
215
+ attr_reader :connection
88
216
 
89
217
  # @param [String] host your statsd host
90
218
  # @param [Integer] port your statsd port
91
- # @option opts [String] :namespace set a namespace to be prepended to every metric name
92
- # @option opts [Array<String>] :tags tags to be added to every metric
93
- def initialize(host = DEFAULT_HOST, port = DEFAULT_PORT, opts = {}, max_buffer_size=50)
94
- self.host, self.port = host, port
95
- @socket_path = opts[:socket_path]
96
- @prefix = nil
97
- @socket = connect_to_socket if @socket_path.nil?
98
- self.namespace = opts[:namespace]
99
- self.tags = opts[:tags]
100
- @buffer = Array.new
101
- self.max_buffer_size = max_buffer_size
102
- @batch_nesting_depth = 0
103
- end
219
+ # @option [String] namespace set a namespace to be prepended to every metric name
220
+ # @option [Array<String>|Hash] tags tags to be added to every metric
221
+ # @option [Logger] logger for debugging
222
+ # @option [Integer] max_buffer_bytes max bytes to buffer when using #batch
223
+ # @option [String] socket_path unix socket path
224
+ # @option [Float] default sample rate if not overridden
225
+ def initialize(
226
+ host = nil,
227
+ port = nil,
228
+ namespace: nil,
229
+ tags: nil,
230
+ max_buffer_bytes: 8192,
231
+ socket_path: nil,
232
+ logger: nil,
233
+ sample_rate: nil
234
+ )
235
+ if socket_path.nil?
236
+ @connection = UDPConnection.new(host, port, logger)
237
+ else
238
+ @connection = UDSConnection.new(socket_path, logger)
239
+ end
240
+ @logger = logger
104
241
 
105
- def namespace=(namespace) #:nodoc:
106
242
  @namespace = namespace
107
- @prefix = namespace.nil? ? nil : "#{namespace}.".freeze
108
- end
243
+ @prefix = @namespace ? "#{@namespace}.".freeze : nil
109
244
 
110
- def host=(host) #:nodoc:
111
- @host = host || DEFAULT_HOST
112
- end
245
+ @sample_rate = sample_rate
113
246
 
114
- def port=(port) #:nodoc:
115
- @port = port || DEFAULT_PORT
116
- end
247
+ unless tags.nil? or tags.is_a? Array or tags.is_a? Hash
248
+ raise ArgumentError, 'tags must be a Array<String> or a Hash'
249
+ end
117
250
 
118
- def tags=(tags) #:nodoc:
119
- raise ArgumentError, 'tags must be a Array<String>' unless tags.nil? or tags.is_a? Array
251
+ tags = tag_hash_to_array(tags) if tags.is_a? Hash
120
252
  @tags = (tags || []).compact.map! {|tag| escape_tag_content(tag)}
253
+
254
+ # append the entity id to tags if DD_ENTITY_ID env var is not nil
255
+ @tags << 'dd.internal.entity_id:' + escape_tag_content(ENV.fetch('DD_ENTITY_ID', nil)) unless ENV.fetch('DD_ENTITY_ID', nil).nil?
256
+
257
+ @batch = Batch.new @connection, max_buffer_bytes
258
+ end
259
+
260
+ # yield a new instance to a block and close it when done
261
+ # for short-term use-cases that don't want to close the socket manually
262
+ def self.open(*args)
263
+ instance = new(*args)
264
+ yield instance
265
+ ensure
266
+ instance.close
121
267
  end
122
268
 
123
269
  # Sends an increment (count = 1) for the given stat to the statsd server.
@@ -128,7 +274,7 @@ module Datadog
128
274
  # @option opts [Array<String>] :tags An array of tags
129
275
  # @option opts [Numeric] :by increment value, default 1
130
276
  # @see #count
131
- def increment(stat, opts={})
277
+ def increment(stat, opts=EMPTY_OPTIONS)
132
278
  opts = {:sample_rate => opts} if opts.is_a? Numeric
133
279
  incr_value = opts.fetch(:by, 1)
134
280
  count stat, incr_value, opts
@@ -142,7 +288,7 @@ module Datadog
142
288
  # @option opts [Array<String>] :tags An array of tags
143
289
  # @option opts [Numeric] :by decrement value, default 1
144
290
  # @see #count
145
- def decrement(stat, opts={})
291
+ def decrement(stat, opts=EMPTY_OPTIONS)
146
292
  opts = {:sample_rate => opts} if opts.is_a? Numeric
147
293
  decr_value = - opts.fetch(:by, 1)
148
294
  count stat, decr_value, opts
@@ -155,7 +301,7 @@ module Datadog
155
301
  # @param [Hash] opts the options to create the metric with
156
302
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
157
303
  # @option opts [Array<String>] :tags An array of tags
158
- def count(stat, count, opts={})
304
+ def count(stat, count, opts=EMPTY_OPTIONS)
159
305
  opts = {:sample_rate => opts} if opts.is_a? Numeric
160
306
  send_stats stat, count, COUNTER_TYPE, opts
161
307
  end
@@ -173,7 +319,7 @@ module Datadog
173
319
  # @option opts [Array<String>] :tags An array of tags
174
320
  # @example Report the current user count:
175
321
  # $statsd.gauge('user.count', User.count)
176
- def gauge(stat, value, opts={})
322
+ def gauge(stat, value, opts=EMPTY_OPTIONS)
177
323
  opts = {:sample_rate => opts} if opts.is_a? Numeric
178
324
  send_stats stat, value, GAUGE_TYPE, opts
179
325
  end
@@ -187,7 +333,7 @@ module Datadog
187
333
  # @option opts [Array<String>] :tags An array of tags
188
334
  # @example Report the current user count:
189
335
  # $statsd.histogram('user.count', User.count)
190
- def histogram(stat, value, opts={})
336
+ def histogram(stat, value, opts=EMPTY_OPTIONS)
191
337
  send_stats stat, value, HISTOGRAM_TYPE, opts
192
338
  end
193
339
 
@@ -203,7 +349,7 @@ module Datadog
203
349
  # @option opts [Array<String>] :tags An array of tags
204
350
  # @example Report the current user count:
205
351
  # $statsd.distribution('user.count', User.count)
206
- def distribution(stat, value, opts={})
352
+ def distribution(stat, value, opts=EMPTY_OPTIONS)
207
353
  send_stats stat, value, DISTRIBUTION_TYPE, opts
208
354
  end
209
355
 
@@ -217,7 +363,7 @@ module Datadog
217
363
  # @param [Hash] opts the options to create the metric with
218
364
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
219
365
  # @option opts [Array<String>] :tags An array of tags
220
- def timing(stat, ms, opts={})
366
+ def timing(stat, ms, opts=EMPTY_OPTIONS)
221
367
  opts = {:sample_rate => opts} if opts.is_a? Numeric
222
368
  send_stats stat, ms, TIMING_TYPE, opts
223
369
  end
@@ -235,7 +381,7 @@ module Datadog
235
381
  # @see #timing
236
382
  # @example Report the time (in ms) taken to activate an account
237
383
  # $statsd.time('account.activate') { @account.activate! }
238
- def time(stat, opts={})
384
+ def time(stat, opts=EMPTY_OPTIONS)
239
385
  opts = {:sample_rate => opts} if opts.is_a? Numeric
240
386
  start = (PROCESS_TIME_SUPPORTED ? Process.clock_gettime(Process::CLOCK_MONOTONIC) : Time.now.to_f)
241
387
  return yield
@@ -243,6 +389,7 @@ module Datadog
243
389
  finished = (PROCESS_TIME_SUPPORTED ? Process.clock_gettime(Process::CLOCK_MONOTONIC) : Time.now.to_f)
244
390
  timing(stat, ((finished - start) * 1000).round, opts)
245
391
  end
392
+
246
393
  # Sends a value to be tracked as a set to the statsd server.
247
394
  #
248
395
  # @param [String] stat stat name.
@@ -252,7 +399,7 @@ module Datadog
252
399
  # @option opts [Array<String>] :tags An array of tags
253
400
  # @example Record a unique visitory by id:
254
401
  # $statsd.set('visitors.uniques', User.id)
255
- def set(stat, value, opts={})
402
+ def set(stat, value, opts=EMPTY_OPTIONS)
256
403
  opts = {:sample_rate => opts} if opts.is_a? Numeric
257
404
  send_stats stat, value, SET_TYPE, opts
258
405
  end
@@ -262,37 +409,14 @@ module Datadog
262
409
  # @param [String] name Service check name
263
410
  # @param [String] status Service check status.
264
411
  # @param [Hash] opts the additional data about the service check
265
- # @option opts [Integer, nil] :timestamp (nil) Assign a timestamp to the event. Default is now when none
266
- # @option opts [String, nil] :hostname (nil) Assign a hostname to the event.
412
+ # @option opts [Integer, String, nil] :timestamp (nil) Assign a timestamp to the service check. Default is now when none
413
+ # @option opts [String, nil] :hostname (nil) Assign a hostname to the service check.
267
414
  # @option opts [Array<String>, nil] :tags (nil) An array of tags
268
415
  # @option opts [String, nil] :message (nil) A message to associate with this service check status
269
416
  # @example Report a critical service check status
270
417
  # $statsd.service_check('my.service.check', Statsd::CRITICAL, :tags=>['urgent'])
271
- def service_check(name, status, opts={})
272
- service_check_string = format_service_check(name, status, opts)
273
- send_to_socket service_check_string
274
- end
275
-
276
- def format_service_check(name, status, opts={})
277
- sc_string = "_sc|#{name}|#{status}"
278
-
279
- SC_OPT_KEYS.each do |key, shorthand_key|
280
- next unless opts[key]
281
-
282
- if key == :tags
283
- if tags_string = tags_as_string(opts)
284
- sc_string << "|##{tags_string}"
285
- end
286
- elsif key == :message
287
- message = remove_pipes(opts[:message])
288
- escaped_message = escape_service_check_message(message)
289
- sc_string << "|m:#{escaped_message}"
290
- else
291
- value = remove_pipes(opts[key])
292
- sc_string << "|#{shorthand_key}#{value}"
293
- end
294
- end
295
- return sc_string
418
+ def service_check(name, status, opts=EMPTY_OPTIONS)
419
+ send_stat format_service_check(name, status, opts)
296
420
  end
297
421
 
298
422
  # This end point allows you to post events to the stream. You can tag them, set priority and even aggregate them with other events.
@@ -302,9 +426,9 @@ module Datadog
302
426
  # it will be grouped with other events that don't have an event type.
303
427
  #
304
428
  # @param [String] title Event title
305
- # @param [String] text Event text. Supports \n
429
+ # @param [String] text Event text. Supports newlines (+\n+)
306
430
  # @param [Hash] opts the additional data about the event
307
- # @option opts [Integer, nil] :date_happened (nil) Assign a timestamp to the event. Default is now when none
431
+ # @option opts [Integer, String, nil] :date_happened (nil) Assign a timestamp to the event. Default is now when none
308
432
  # @option opts [String, nil] :hostname (nil) Assign a hostname to the event.
309
433
  # @option opts [String, nil] :aggregation_key (nil) Assign an aggregation key to the event, to group it with some others
310
434
  # @option opts [String, nil] :priority ('normal') Can be "normal" or "low"
@@ -313,11 +437,8 @@ module Datadog
313
437
  # @option opts [Array<String>] :tags tags to be added to every metric
314
438
  # @example Report an awful event:
315
439
  # $statsd.event('Something terrible happened', 'The end is near if we do nothing', :alert_type=>'warning', :tags=>['end_of_times','urgent'])
316
- def event(title, text, opts={})
317
- event_string = format_event(title, text, opts)
318
- raise "Event #{title} payload is too big (more that 8KB), event discarded" if event_string.length > 8 * 1024
319
-
320
- send_to_socket event_string
440
+ def event(title, text, opts=EMPTY_OPTIONS)
441
+ send_stat format_event(title, text, opts)
321
442
  end
322
443
 
323
444
  # Send several metrics in the same UDP Packet
@@ -328,24 +449,73 @@ module Datadog
328
449
  # s.gauge('users.online',156)
329
450
  # s.increment('page.views')
330
451
  # end
331
- def batch()
332
- @batch_nesting_depth += 1
333
- yield self
334
- ensure
335
- @batch_nesting_depth -= 1
336
- flush_buffer if @batch_nesting_depth == 0
452
+ def batch
453
+ @batch.open { yield self }
454
+ end
455
+
456
+ # Close the underlying socket
457
+ def close
458
+ @connection.close
459
+ end
460
+
461
+ private
462
+
463
+ NEW_LINE = "\n".freeze
464
+ ESC_NEW_LINE = "\\n".freeze
465
+ COMMA = ",".freeze
466
+ PIPE = "|".freeze
467
+ DOT = ".".freeze
468
+ DOUBLE_COLON = "::".freeze
469
+ UNDERSCORE = "_".freeze
470
+ PROCESS_TIME_SUPPORTED = (RUBY_VERSION >= "2.1.0")
471
+ EMPTY_OPTIONS = {}.freeze
472
+
473
+ private_constant :NEW_LINE, :ESC_NEW_LINE, :COMMA, :PIPE, :DOT,
474
+ :DOUBLE_COLON, :UNDERSCORE, :EMPTY_OPTIONS
475
+
476
+ def format_service_check(name, status, opts=EMPTY_OPTIONS)
477
+ sc_string = "_sc|#{name}|#{status}".dup
478
+
479
+ SC_OPT_KEYS.each do |key, shorthand_key|
480
+ next unless opts[key]
481
+
482
+ if key == :tags
483
+ if tags_string = tags_as_string(opts)
484
+ sc_string << "|##{tags_string}"
485
+ end
486
+ elsif key == :message
487
+ message = remove_pipes(opts[:message])
488
+ escaped_message = escape_service_check_message(message)
489
+ sc_string << "|m:#{escaped_message}"
490
+ else
491
+ if key == :timestamp && opts[key].is_a?(Integer)
492
+ value = opts[key]
493
+ else
494
+ value = remove_pipes(opts[key])
495
+ end
496
+ sc_string << "|#{shorthand_key}#{value}"
497
+ end
498
+ end
499
+ sc_string
337
500
  end
338
501
 
339
- def format_event(title, text, opts={})
502
+ def format_event(title, text, opts=EMPTY_OPTIONS)
340
503
  escaped_title = escape_event_content(title)
341
504
  escaped_text = escape_event_content(text)
342
- event_string_data = "_e{#{escaped_title.length},#{escaped_text.length}}:#{escaped_title}|#{escaped_text}"
505
+ event_string_data = "_e{#{escaped_title.bytesize},#{escaped_text.bytesize}}:#{escaped_title}|#{escaped_text}".dup
343
506
 
344
507
  # We construct the string to be sent by adding '|key:value' parts to it when needed
345
508
  # All pipes ('|') in the metadata are removed. Title and Text can keep theirs
346
509
  OPTS_KEYS.each do |key, shorthand_key|
347
510
  if key != :tags && opts[key]
348
- value = remove_pipes(opts[key])
511
+ # :date_happened is the only key where the value is an Integer
512
+ # To not break backwards compatibility, we still accept a String
513
+ if key == :date_happened && opts[key].is_a?(Integer)
514
+ value = opts[key]
515
+ # All other keys only have String values
516
+ else
517
+ value = remove_pipes(opts[key])
518
+ end
349
519
  event_string_data << "|#{shorthand_key}:#{value}"
350
520
  end
351
521
  end
@@ -355,36 +525,25 @@ module Datadog
355
525
  event_string_data << "|##{tags_string}"
356
526
  end
357
527
 
358
- raise "Event #{title} payload is too big (more that 8KB), event discarded" if event_string_data.length > 8192 # 8 * 1024 = 8192
359
- return event_string_data
360
- end
361
-
362
- # Close the underlying socket
363
- def close()
364
- @socket.close
528
+ raise "Event #{title} payload is too big (more that 8KB), event discarded" if event_string_data.bytesize > MAX_EVENT_SIZE
529
+ event_string_data
365
530
  end
366
531
 
367
- private
368
-
369
- NEW_LINE = "\n".freeze
370
- ESC_NEW_LINE = "\\n".freeze
371
- COMMA = ",".freeze
372
- PIPE = "|".freeze
373
- DOT = ".".freeze
374
- DOUBLE_COLON = "::".freeze
375
- UNDERSCORE = "_".freeze
376
- PROCESS_TIME_SUPPORTED = (RUBY_VERSION >= "2.1.0")
377
-
378
- private_constant :NEW_LINE, :ESC_NEW_LINE, :COMMA, :PIPE, :DOT,
379
- :DOUBLE_COLON, :UNDERSCORE
380
-
381
532
  def tags_as_string(opts)
382
- tag_arr = opts[:tags] || []
383
- tag_arr = tag_arr.map { |tag| escape_tag_content(tag) }
384
- tag_arr = tags + tag_arr # @tags are normalized when set, so not need to normalize them again
533
+ if tag_arr = opts[:tags]
534
+ tag_arr = tag_hash_to_array(tag_arr) if tag_arr.is_a? Hash
535
+ tag_arr = tag_arr.map { |tag| escape_tag_content(tag) }
536
+ tag_arr = tags + tag_arr # @tags are normalized when set, so not need to normalize them again
537
+ else
538
+ tag_arr = tags
539
+ end
385
540
  tag_arr.join(COMMA) unless tag_arr.empty?
386
541
  end
387
542
 
543
+ def tag_hash_to_array(tag_hash)
544
+ tag_hash.to_a.map {|pair| pair.compact.join(":")}
545
+ end
546
+
388
547
  def escape_event_content(msg)
389
548
  msg.gsub NEW_LINE, ESC_NEW_LINE
390
549
  end
@@ -403,10 +562,10 @@ module Datadog
403
562
  escape_event_content(msg).gsub('m:'.freeze, 'm\:'.freeze)
404
563
  end
405
564
 
406
- def send_stats(stat, delta, type, opts={})
407
- sample_rate = opts[:sample_rate] || 1
408
- if sample_rate == 1 or rand < sample_rate
409
- full_stat = ''
565
+ def send_stats(stat, delta, type, opts=EMPTY_OPTIONS)
566
+ sample_rate = opts[:sample_rate] || @sample_rate || 1
567
+ if sample_rate == 1 or rand <= sample_rate
568
+ full_stat = ''.dup
410
569
  full_stat << @prefix if @prefix
411
570
 
412
571
  stat = stat.is_a?(String) ? stat.dup : stat.to_s
@@ -437,62 +596,11 @@ module Datadog
437
596
  end
438
597
 
439
598
  def send_stat(message)
440
- if @batch_nesting_depth > 0
441
- @buffer << message
442
- flush_buffer if @buffer.length >= @max_buffer_size
599
+ if @batch.open?
600
+ @batch.add message
443
601
  else
444
- send_to_socket(message)
445
- end
446
- end
447
-
448
- def flush_buffer
449
- return @buffer if @buffer.empty?
450
- send_to_socket(@buffer.join(NEW_LINE))
451
- @buffer = Array.new
452
- end
453
-
454
- def connect_to_socket
455
- if !@socket_path.nil?
456
- socket = Socket.new(Socket::AF_UNIX, Socket::SOCK_DGRAM)
457
- socket.connect(Socket.pack_sockaddr_un(@socket_path))
458
- else
459
- socket = UDPSocket.new
460
- socket.connect(@host, @port)
602
+ @connection.write(message)
461
603
  end
462
- socket
463
- end
464
-
465
- def sock
466
- @socket ||= connect_to_socket
467
- end
468
-
469
- def send_to_socket(message)
470
- self.class.logger.debug { "Statsd: #{message}" } if self.class.logger
471
- if @socket_path.nil?
472
- sock.send(message, 0)
473
- else
474
- sock.sendmsg_nonblock(message)
475
- end
476
- rescue => boom
477
- if @socket_path && (boom.is_a?(Errno::ECONNREFUSED) ||
478
- boom.is_a?(Errno::ECONNRESET) ||
479
- boom.is_a?(Errno::ENOENT))
480
- return @socket = nil
481
- end
482
- # Try once to reconnect if the socket has been closed
483
- retries ||= 1
484
- if retries <= 1 && boom.is_a?(IOError) && boom.message =~ /closed stream/i
485
- retries += 1
486
- begin
487
- @socket = connect_to_socket
488
- retry
489
- rescue => e
490
- boom = e
491
- end
492
- end
493
-
494
- self.class.logger.error { "Statsd: #{boom.class} #{boom}" } if self.class.logger
495
- nil
496
604
  end
497
605
  end
498
606
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dogstatsd-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.0
4
+ version: 4.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rein Henrichs
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-02-04 00:00:00.000000000 Z
11
+ date: 2019-08-22 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A Ruby DogStastd client
14
14
  email: code@datadoghq.com
@@ -33,7 +33,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
33
33
  requirements:
34
34
  - - ">="
35
35
  - !ruby/object:Gem::Version
36
- version: '0'
36
+ version: 2.0.0
37
37
  required_rubygems_version: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - ">="
@@ -41,7 +41,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
41
41
  version: '0'
42
42
  requirements: []
43
43
  rubyforge_project:
44
- rubygems_version: 2.7.1
44
+ rubygems_version: 2.7.6
45
45
  signing_key:
46
46
  specification_version: 4
47
47
  summary: A Ruby DogStatsd client