dogstatsd-ruby 3.3.0 → 4.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +34 -5
- data/lib/datadog/statsd.rb +290 -182
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fde2be6b614574c2555c8cbbd2ca16ef33bcd09837502f482f5f6baf582fd67b
|
4
|
+
data.tar.gz: cc1523c5aa78643d93fc891116c46bb5322dda297f1676cd73d201c9beee2a26
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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, :
|
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, :
|
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(
|
69
|
+
statsd.event('There might be a storm tomorrow', 'A friend warned me earlier.')
|
61
70
|
|
62
71
|
# Cry for help
|
63
|
-
statsd.event(
|
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
|
|
data/lib/datadog/statsd.rb
CHANGED
@@ -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, :
|
19
|
+
# statsd = Datadog::Statsd.new 'localhost', 8125, tags: 'tag1:true'
|
19
20
|
module Datadog
|
20
21
|
class Statsd
|
21
22
|
|
22
|
-
|
23
|
-
|
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 = "
|
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
|
76
|
-
|
208
|
+
# Maximum buffer size in bytes before it is flushed
|
209
|
+
attr_reader :max_buffer_bytes
|
77
210
|
|
78
|
-
|
79
|
-
|
80
|
-
attr_accessor :logger
|
81
|
-
end
|
211
|
+
# Default sample rate
|
212
|
+
attr_reader :sample_rate
|
82
213
|
|
83
|
-
#
|
84
|
-
|
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
|
92
|
-
# @option
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
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
|
108
|
-
end
|
243
|
+
@prefix = @namespace ? "#{@namespace}.".freeze : nil
|
109
244
|
|
110
|
-
|
111
|
-
@host = host || DEFAULT_HOST
|
112
|
-
end
|
245
|
+
@sample_rate = sample_rate
|
113
246
|
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
-
|
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
|
266
|
-
# @option opts [String, nil] :hostname (nil) Assign a hostname to the
|
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
|
-
|
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
|
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
|
-
|
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
|
-
@
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
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.
|
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
|
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.
|
359
|
-
|
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
|
-
|
384
|
-
|
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
|
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 @
|
441
|
-
@
|
442
|
-
flush_buffer if @buffer.length >= @max_buffer_size
|
599
|
+
if @batch.open?
|
600
|
+
@batch.add message
|
443
601
|
else
|
444
|
-
|
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:
|
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:
|
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:
|
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.
|
44
|
+
rubygems_version: 2.7.6
|
45
45
|
signing_key:
|
46
46
|
specification_version: 4
|
47
47
|
summary: A Ruby DogStatsd client
|