dogstatsd-ruby 4.0.0 → 5.3.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
  require 'socket'
3
3
 
4
+ require_relative 'statsd/version'
5
+ require_relative 'statsd/telemetry'
6
+ require_relative 'statsd/udp_connection'
7
+ require_relative 'statsd/uds_connection'
8
+ require_relative 'statsd/connection_cfg'
9
+ require_relative 'statsd/message_buffer'
10
+ require_relative 'statsd/serialization'
11
+ require_relative 'statsd/sender'
12
+ require_relative 'statsd/single_thread_sender'
13
+ require_relative 'statsd/forwarder'
14
+ require_relative 'statsd/timer'
15
+
16
+ $deprecation_message_mutex = Mutex.new
17
+ $deprecation_message_done = false
18
+
4
19
  # = Datadog::Statsd: A DogStatsd client (https://www.datadoghq.com)
5
20
  #
6
21
  # @example Set up a global Statsd client for a server on localhost:8125
@@ -19,216 +34,129 @@ require 'socket'
19
34
  # statsd = Datadog::Statsd.new 'localhost', 8125, tags: 'tag1:true'
20
35
  module Datadog
21
36
  class Statsd
22
-
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
- def initialize(host, port, socket_path, logger)
37
- @host = host || DEFAULT_HOST
38
- @port = port || DEFAULT_PORT
39
- @socket_path = socket_path
40
- @logger = logger
41
- end
42
-
43
- def write(message)
44
- @logger.debug { "Statsd: #{message}" } if @logger
45
- if @socket_path.nil?
46
- socket.send(message, 0)
47
- else
48
- socket.sendmsg_nonblock(message)
49
- end
50
- rescue StandardError => boom
51
- # Give up on this socket if it looks like it is bad
52
- bad_socket = !@socket_path.nil? && (
53
- boom.is_a?(Errno::ECONNREFUSED) ||
54
- boom.is_a?(Errno::ECONNRESET) ||
55
- boom.is_a?(Errno::ENOENT)
56
- )
57
- if bad_socket
58
- @socket = nil
59
- return
60
- end
61
-
62
- # Try once to reconnect if the socket has been closed
63
- retries ||= 1
64
- if retries <= 1 && boom.is_a?(IOError) && boom.message =~ /closed stream/i
65
- retries += 1
66
- begin
67
- @socket = connect
68
- retry
69
- rescue StandardError => e
70
- boom = e
71
- end
72
- end
73
-
74
- @logger.error { "Statsd: #{boom.class} #{boom}" } if @logger
75
- nil
76
- end
77
-
78
- # Close the underlying socket
79
- def close
80
- @socket && @socket.close
81
- end
82
-
83
- private
84
-
85
- def socket
86
- @socket ||= connect
87
- end
88
-
89
- def connect
90
- if @socket_path.nil?
91
- socket = UDPSocket.new
92
- socket.connect(@host, @port)
93
- else
94
- socket = Socket.new(Socket::AF_UNIX, Socket::SOCK_DGRAM)
95
- socket.connect(Socket.pack_sockaddr_un(@socket_path))
96
- end
97
- socket
98
- end
37
+ class Error < StandardError
99
38
  end
100
39
 
101
- class Batch
102
- def initialize(connection, max_buffer_bytes)
103
- @connection = connection
104
- @max_buffer_bytes = max_buffer_bytes
105
- @depth = 0
106
- reset
107
- end
40
+ OK = 0
41
+ WARNING = 1
42
+ CRITICAL = 2
43
+ UNKNOWN = 3
108
44
 
109
- def open
110
- @depth += 1
111
- yield
112
- ensure
113
- @depth -= 1
114
- flush if !open?
115
- end
45
+ UDP_DEFAULT_BUFFER_SIZE = 1_432
46
+ UDS_DEFAULT_BUFFER_SIZE = 8_192
47
+ DEFAULT_BUFFER_POOL_SIZE = Float::INFINITY
116
48
 
117
- def open?
118
- @depth > 0
119
- end
49
+ UDP_DEFAULT_SENDER_QUEUE_SIZE = 2048
50
+ UDS_DEFAULT_SENDER_QUEUE_SIZE = 512
120
51
 
121
- def add(message)
122
- message_bytes = message.bytesize
52
+ MAX_EVENT_SIZE = 8 * 1_024
123
53
 
124
- unless @buffer_bytes == 0
125
- if @buffer_bytes + 1 + message_bytes >= @max_buffer_bytes
126
- flush
127
- else
128
- @buffer << NEW_LINE
129
- @buffer_bytes += 1
130
- end
131
- end
54
+ # minimum flush interval for the telemetry in seconds
55
+ DEFAULT_TELEMETRY_FLUSH_INTERVAL = 10
132
56
 
133
- @buffer << message
134
- @buffer_bytes += message_bytes
135
- end
136
-
137
- def flush
138
- return if @buffer_bytes == 0
139
- @connection.write @buffer
140
- reset
141
- end
142
-
143
- private
144
-
145
- def reset
146
- @buffer = String.new
147
- @buffer_bytes = 0
148
- end
149
- end
150
-
151
- # Create a dictionary to assign a key to every parameter's name, except for tags (treated differently)
152
- # Goal: Simple and fast to add some other parameters
153
- OPTS_KEYS = {
154
- :date_happened => :d,
155
- :hostname => :h,
156
- :aggregation_key => :k,
157
- :priority => :p,
158
- :source_type_name => :s,
159
- :alert_type => :t,
160
- }
161
-
162
- # Service check options
163
- SC_OPT_KEYS = {
164
- :timestamp => 'd:'.freeze,
165
- :hostname => 'h:'.freeze,
166
- :tags => '#'.freeze,
167
- :message => 'm:'.freeze,
168
- }
169
-
170
- OK = 0
171
- WARNING = 1
172
- CRITICAL = 2
173
- UNKNOWN = 3
174
-
175
- MAX_EVENT_SIZE = 8 * 1024
176
-
177
- COUNTER_TYPE = 'c'.freeze
178
- GAUGE_TYPE = 'g'.freeze
179
- HISTOGRAM_TYPE = 'h'.freeze
180
- DISTRIBUTION_TYPE = 'd'.freeze
181
- TIMING_TYPE = 'ms'.freeze
182
- SET_TYPE = 's'.freeze
183
- VERSION = "4.0.0".freeze
57
+ COUNTER_TYPE = 'c'
58
+ GAUGE_TYPE = 'g'
59
+ HISTOGRAM_TYPE = 'h'
60
+ DISTRIBUTION_TYPE = 'd'
61
+ TIMING_TYPE = 'ms'
62
+ SET_TYPE = 's'
184
63
 
185
64
  # A namespace to prepend to all statsd calls. Defaults to no namespace.
186
65
  attr_reader :namespace
187
66
 
188
67
  # Global tags to be added to every statsd call. Defaults to no tags.
189
- attr_reader :tags
190
-
191
- # Buffer containing the statsd message before they are sent in batch
192
- attr_reader :buffer
193
-
194
- # Maximum buffer size in bytes before it is flushed
195
- attr_reader :max_buffer_bytes
68
+ def tags
69
+ serializer.global_tags
70
+ end
196
71
 
197
- # Connection
198
- attr_reader :connection
72
+ # Default sample rate
73
+ attr_reader :sample_rate
199
74
 
200
75
  # @param [String] host your statsd host
201
76
  # @param [Integer] port your statsd port
202
77
  # @option [String] namespace set a namespace to be prepended to every metric name
203
- # @option [Array<String>] tags tags to be added to every metric
204
- # @option [Loger] logger for debugging
205
- # @option [Integer] max_buffer_bytes max bytes to buffer when using #batch
78
+ # @option [Array<String>|Hash] tags tags to be added to every metric
79
+ # @option [Logger] logger for debugging
80
+ # @option [Integer] buffer_max_payload_size max bytes to buffer
81
+ # @option [Integer] buffer_max_pool_size max messages to buffer
82
+ # @option [Integer] sender_queue_size size of the sender queue in number of buffers (multi-thread only)
83
+ # @option [Numeric] buffer_flush_interval interval in second to flush buffer
206
84
  # @option [String] socket_path unix socket path
85
+ # @option [Float] default sample rate if not overridden
86
+ # @option [Boolean] single_thread flushes the metrics on the main thread instead of in a companion thread
207
87
  def initialize(
208
88
  host = nil,
209
89
  port = nil,
90
+ socket_path: nil,
91
+
210
92
  namespace: nil,
211
93
  tags: nil,
212
- max_buffer_bytes: 8192,
213
- socket_path: nil,
214
- logger: nil
94
+ sample_rate: nil,
95
+
96
+ buffer_max_payload_size: nil,
97
+ buffer_max_pool_size: nil,
98
+ buffer_overflowing_stategy: :drop,
99
+ buffer_flush_interval: nil,
100
+
101
+ sender_queue_size: nil,
102
+
103
+ logger: nil,
104
+
105
+ single_thread: false,
106
+
107
+ telemetry_enable: true,
108
+ telemetry_flush_interval: DEFAULT_TELEMETRY_FLUSH_INTERVAL
215
109
  )
216
- @connection = Connection.new(host, port, socket_path, logger)
217
- @logger = logger
110
+ unless tags.nil? || tags.is_a?(Array) || tags.is_a?(Hash)
111
+ raise ArgumentError, 'tags must be an array of string tags or a Hash'
112
+ end
218
113
 
219
114
  @namespace = namespace
220
115
  @prefix = @namespace ? "#{@namespace}.".freeze : nil
116
+ @serializer = Serialization::Serializer.new(prefix: @prefix, global_tags: tags)
117
+ @sample_rate = sample_rate
118
+
119
+ # deprecation message for ruby < 2.1.0 users as we will drop support for ruby 2.0
120
+ # in dogstatsd-ruby 5.4.0
121
+ # TODO(remy): remove this message and the two global vars used in dogstatd-ruby 5.4.0
122
+ if RUBY_VERSION < '2.1.0' && $deprecation_message_mutex.try_lock && !$deprecation_message_done
123
+ if logger != nil
124
+ logger.warn { "deprecation: dogstatsd-ruby will drop support of Ruby < 2.1.0 in a next minor release" }
125
+ else
126
+ puts("warning: deprecation: dogstatsd-ruby will drop support of Ruby < 2.1.0 in a next minor release")
127
+ end
128
+ $deprecation_message_done = true
129
+ $deprecation_message_mutex.unlock
130
+ end
131
+
132
+ @forwarder = Forwarder.new(
133
+ connection_cfg: ConnectionCfg.new(
134
+ host: host,
135
+ port: port,
136
+ socket_path: socket_path,
137
+ ),
138
+
139
+ global_tags: tags,
140
+ logger: logger,
141
+
142
+ single_thread: single_thread,
221
143
 
222
- raise ArgumentError, 'tags must be a Array<String>' unless tags.nil? or tags.is_a? Array
223
- @tags = (tags || []).compact.map! {|tag| escape_tag_content(tag)}
144
+ buffer_max_payload_size: buffer_max_payload_size,
145
+ buffer_max_pool_size: buffer_max_pool_size,
146
+ buffer_overflowing_stategy: buffer_overflowing_stategy,
147
+ buffer_flush_interval: buffer_flush_interval,
224
148
 
225
- @batch = Batch.new @connection, max_buffer_bytes
149
+ sender_queue_size: sender_queue_size,
150
+
151
+ telemetry_flush_interval: telemetry_enable ? telemetry_flush_interval : nil,
152
+ )
226
153
  end
227
154
 
228
155
  # yield a new instance to a block and close it when done
229
156
  # for short-term use-cases that don't want to close the socket manually
230
157
  def self.open(*args)
231
158
  instance = new(*args)
159
+
232
160
  yield instance
233
161
  ensure
234
162
  instance.close
@@ -242,10 +170,10 @@ module Datadog
242
170
  # @option opts [Array<String>] :tags An array of tags
243
171
  # @option opts [Numeric] :by increment value, default 1
244
172
  # @see #count
245
- def increment(stat, opts=EMPTY_OPTIONS)
246
- opts = {:sample_rate => opts} if opts.is_a? Numeric
173
+ def increment(stat, opts = EMPTY_OPTIONS)
174
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
247
175
  incr_value = opts.fetch(:by, 1)
248
- count stat, incr_value, opts
176
+ count(stat, incr_value, opts)
249
177
  end
250
178
 
251
179
  # Sends a decrement (count = -1) for the given stat to the statsd server.
@@ -256,10 +184,10 @@ module Datadog
256
184
  # @option opts [Array<String>] :tags An array of tags
257
185
  # @option opts [Numeric] :by decrement value, default 1
258
186
  # @see #count
259
- def decrement(stat, opts=EMPTY_OPTIONS)
260
- opts = {:sample_rate => opts} if opts.is_a? Numeric
187
+ def decrement(stat, opts = EMPTY_OPTIONS)
188
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
261
189
  decr_value = - opts.fetch(:by, 1)
262
- count stat, decr_value, opts
190
+ count(stat, decr_value, opts)
263
191
  end
264
192
 
265
193
  # Sends an arbitrary count for the given stat to the statsd server.
@@ -269,9 +197,9 @@ module Datadog
269
197
  # @param [Hash] opts the options to create the metric with
270
198
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
271
199
  # @option opts [Array<String>] :tags An array of tags
272
- def count(stat, count, opts=EMPTY_OPTIONS)
273
- opts = {:sample_rate => opts} if opts.is_a? Numeric
274
- send_stats stat, count, COUNTER_TYPE, opts
200
+ def count(stat, count, opts = EMPTY_OPTIONS)
201
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
202
+ send_stats(stat, count, COUNTER_TYPE, opts)
275
203
  end
276
204
 
277
205
  # Sends an arbitary gauge value for the given stat to the statsd server.
@@ -287,9 +215,9 @@ module Datadog
287
215
  # @option opts [Array<String>] :tags An array of tags
288
216
  # @example Report the current user count:
289
217
  # $statsd.gauge('user.count', User.count)
290
- def gauge(stat, value, opts=EMPTY_OPTIONS)
291
- opts = {:sample_rate => opts} if opts.is_a? Numeric
292
- send_stats stat, value, GAUGE_TYPE, opts
218
+ def gauge(stat, value, opts = EMPTY_OPTIONS)
219
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
220
+ send_stats(stat, value, GAUGE_TYPE, opts)
293
221
  end
294
222
 
295
223
  # Sends a value to be tracked as a histogram to the statsd server.
@@ -301,14 +229,11 @@ module Datadog
301
229
  # @option opts [Array<String>] :tags An array of tags
302
230
  # @example Report the current user count:
303
231
  # $statsd.histogram('user.count', User.count)
304
- def histogram(stat, value, opts=EMPTY_OPTIONS)
305
- send_stats stat, value, HISTOGRAM_TYPE, opts
232
+ def histogram(stat, value, opts = EMPTY_OPTIONS)
233
+ send_stats(stat, value, HISTOGRAM_TYPE, opts)
306
234
  end
307
235
 
308
236
  # Sends a value to be tracked as a distribution to the statsd server.
309
- # Note: Distributions are a beta feature of Datadog and not generally
310
- # available. Distributions must be specifically enabled for your
311
- # organization.
312
237
  #
313
238
  # @param [String] stat stat name.
314
239
  # @param [Numeric] value distribution value.
@@ -317,8 +242,8 @@ module Datadog
317
242
  # @option opts [Array<String>] :tags An array of tags
318
243
  # @example Report the current user count:
319
244
  # $statsd.distribution('user.count', User.count)
320
- def distribution(stat, value, opts=EMPTY_OPTIONS)
321
- send_stats stat, value, DISTRIBUTION_TYPE, opts
245
+ def distribution(stat, value, opts = EMPTY_OPTIONS)
246
+ send_stats(stat, value, DISTRIBUTION_TYPE, opts)
322
247
  end
323
248
 
324
249
  # Sends a timing (in ms) for the given stat to the statsd server. The
@@ -331,9 +256,9 @@ module Datadog
331
256
  # @param [Hash] opts the options to create the metric with
332
257
  # @option opts [Numeric] :sample_rate sample rate, 1 for always
333
258
  # @option opts [Array<String>] :tags An array of tags
334
- def timing(stat, ms, opts=EMPTY_OPTIONS)
335
- opts = {:sample_rate => opts} if opts.is_a? Numeric
336
- send_stats stat, ms, TIMING_TYPE, opts
259
+ def timing(stat, ms, opts = EMPTY_OPTIONS)
260
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
261
+ send_stats(stat, ms, TIMING_TYPE, opts)
337
262
  end
338
263
 
339
264
  # Reports execution time of the provided block using {#timing}.
@@ -349,13 +274,12 @@ module Datadog
349
274
  # @see #timing
350
275
  # @example Report the time (in ms) taken to activate an account
351
276
  # $statsd.time('account.activate') { @account.activate! }
352
- def time(stat, opts=EMPTY_OPTIONS)
353
- opts = {:sample_rate => opts} if opts.is_a? Numeric
354
- start = (PROCESS_TIME_SUPPORTED ? Process.clock_gettime(Process::CLOCK_MONOTONIC) : Time.now.to_f)
355
- return yield
277
+ def time(stat, opts = EMPTY_OPTIONS)
278
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
279
+ start = now
280
+ yield
356
281
  ensure
357
- finished = (PROCESS_TIME_SUPPORTED ? Process.clock_gettime(Process::CLOCK_MONOTONIC) : Time.now.to_f)
358
- timing(stat, ((finished - start) * 1000).round, opts)
282
+ timing(stat, ((now - start) * 1000).round, opts)
359
283
  end
360
284
 
361
285
  # Sends a value to be tracked as a set to the statsd server.
@@ -367,9 +291,9 @@ module Datadog
367
291
  # @option opts [Array<String>] :tags An array of tags
368
292
  # @example Record a unique visitory by id:
369
293
  # $statsd.set('visitors.uniques', User.id)
370
- def set(stat, value, opts=EMPTY_OPTIONS)
371
- opts = {:sample_rate => opts} if opts.is_a? Numeric
372
- send_stats stat, value, SET_TYPE, opts
294
+ def set(stat, value, opts = EMPTY_OPTIONS)
295
+ opts = { sample_rate: opts } if opts.is_a?(Numeric)
296
+ send_stats(stat, value, SET_TYPE, opts)
373
297
  end
374
298
 
375
299
  # This method allows you to send custom service check statuses.
@@ -377,14 +301,16 @@ module Datadog
377
301
  # @param [String] name Service check name
378
302
  # @param [String] status Service check status.
379
303
  # @param [Hash] opts the additional data about the service check
380
- # @option opts [Integer, nil] :timestamp (nil) Assign a timestamp to the event. Default is now when none
381
- # @option opts [String, nil] :hostname (nil) Assign a hostname to the event.
304
+ # @option opts [Integer, String, nil] :timestamp (nil) Assign a timestamp to the service check. Default is now when none
305
+ # @option opts [String, nil] :hostname (nil) Assign a hostname to the service check.
382
306
  # @option opts [Array<String>, nil] :tags (nil) An array of tags
383
307
  # @option opts [String, nil] :message (nil) A message to associate with this service check status
384
308
  # @example Report a critical service check status
385
309
  # $statsd.service_check('my.service.check', Statsd::CRITICAL, :tags=>['urgent'])
386
- def service_check(name, status, opts=EMPTY_OPTIONS)
387
- send_stat format_service_check(name, status, opts)
310
+ def service_check(name, status, opts = EMPTY_OPTIONS)
311
+ telemetry.sent(service_checks: 1) if telemetry
312
+
313
+ forwarder.send_message(serializer.to_service_check(name, status, opts))
388
314
  end
389
315
 
390
316
  # This end point allows you to post events to the stream. You can tag them, set priority and even aggregate them with other events.
@@ -394,23 +320,33 @@ module Datadog
394
320
  # it will be grouped with other events that don't have an event type.
395
321
  #
396
322
  # @param [String] title Event title
397
- # @param [String] text Event text. Supports \n
323
+ # @param [String] text Event text. Supports newlines (+\n+)
398
324
  # @param [Hash] opts the additional data about the event
399
- # @option opts [Integer, nil] :date_happened (nil) Assign a timestamp to the event. Default is now when none
325
+ # @option opts [Integer, String, nil] :date_happened (nil) Assign a timestamp to the event. Default is now when none
400
326
  # @option opts [String, nil] :hostname (nil) Assign a hostname to the event.
401
327
  # @option opts [String, nil] :aggregation_key (nil) Assign an aggregation key to the event, to group it with some others
402
328
  # @option opts [String, nil] :priority ('normal') Can be "normal" or "low"
403
329
  # @option opts [String, nil] :source_type_name (nil) Assign a source type to the event
404
330
  # @option opts [String, nil] :alert_type ('info') Can be "error", "warning", "info" or "success".
331
+ # @option opts [Boolean, false] :truncate_if_too_long (false) Truncate the event if it is too long
405
332
  # @option opts [Array<String>] :tags tags to be added to every metric
406
333
  # @example Report an awful event:
407
334
  # $statsd.event('Something terrible happened', 'The end is near if we do nothing', :alert_type=>'warning', :tags=>['end_of_times','urgent'])
408
- def event(title, text, opts=EMPTY_OPTIONS)
409
- send_stat format_event(title, text, opts)
335
+ def event(title, text, opts = EMPTY_OPTIONS)
336
+ telemetry.sent(events: 1) if telemetry
337
+
338
+ forwarder.send_message(serializer.to_event(title, text, opts))
410
339
  end
411
340
 
412
- # Send several metrics in the same UDP Packet
413
- # They will be buffered and flushed when the block finishes
341
+ # Send several metrics in the same packet.
342
+ # They will be buffered and flushed when the block finishes.
343
+ #
344
+ # This method exists for compatibility with v4.x versions, it is not needed
345
+ # anymore since the batching is now automatically done internally.
346
+ # It also means that an automatic flush could occur if the buffer is filled
347
+ # during the execution of the batch block.
348
+ #
349
+ # This method is DEPRECATED and will be removed in future v6.x API.
414
350
  #
415
351
  # @example Send several metrics in one packet:
416
352
  # $statsd.batch do |s|
@@ -418,140 +354,73 @@ module Datadog
418
354
  # s.increment('page.views')
419
355
  # end
420
356
  def batch
421
- @batch.open { yield self }
357
+ yield self
358
+ flush(sync: true)
422
359
  end
423
360
 
424
361
  # Close the underlying socket
425
- def close
426
- @connection.close
362
+ #
363
+ # @param [Boolean, true] flush Should we flush the metrics before closing
364
+ def close(flush: true)
365
+ flush(sync: true) if flush
366
+ forwarder.close
427
367
  end
428
368
 
429
- private
430
-
431
- NEW_LINE = "\n".freeze
432
- ESC_NEW_LINE = "\\n".freeze
433
- COMMA = ",".freeze
434
- PIPE = "|".freeze
435
- DOT = ".".freeze
436
- DOUBLE_COLON = "::".freeze
437
- UNDERSCORE = "_".freeze
438
- PROCESS_TIME_SUPPORTED = (RUBY_VERSION >= "2.1.0")
439
- EMPTY_OPTIONS = {}.freeze
440
-
441
- private_constant :NEW_LINE, :ESC_NEW_LINE, :COMMA, :PIPE, :DOT,
442
- :DOUBLE_COLON, :UNDERSCORE, :EMPTY_OPTIONS
443
-
444
- def format_service_check(name, status, opts=EMPTY_OPTIONS)
445
- sc_string = "_sc|#{name}|#{status}".dup
446
-
447
- SC_OPT_KEYS.each do |key, shorthand_key|
448
- next unless opts[key]
449
-
450
- if key == :tags
451
- if tags_string = tags_as_string(opts)
452
- sc_string << "|##{tags_string}"
453
- end
454
- elsif key == :message
455
- message = remove_pipes(opts[:message])
456
- escaped_message = escape_service_check_message(message)
457
- sc_string << "|m:#{escaped_message}"
458
- else
459
- value = remove_pipes(opts[key])
460
- sc_string << "|#{shorthand_key}#{value}"
461
- end
462
- end
463
- sc_string
369
+ def sync_with_outbound_io
370
+ forwarder.sync_with_outbound_io
464
371
  end
465
372
 
466
- def format_event(title, text, opts=EMPTY_OPTIONS)
467
- escaped_title = escape_event_content(title)
468
- escaped_text = escape_event_content(text)
469
- event_string_data = "_e{#{escaped_title.length},#{escaped_text.length}}:#{escaped_title}|#{escaped_text}".dup
470
-
471
- # We construct the string to be sent by adding '|key:value' parts to it when needed
472
- # All pipes ('|') in the metadata are removed. Title and Text can keep theirs
473
- OPTS_KEYS.each do |key, shorthand_key|
474
- if key != :tags && opts[key]
475
- value = remove_pipes(opts[key])
476
- event_string_data << "|#{shorthand_key}:#{value}"
477
- end
478
- end
479
-
480
- # Tags are joined and added as last part to the string to be sent
481
- if tags_string = tags_as_string(opts)
482
- event_string_data << "|##{tags_string}"
483
- end
484
-
485
- raise "Event #{title} payload is too big (more that 8KB), event discarded" if event_string_data.length > MAX_EVENT_SIZE
486
- event_string_data
373
+ # Flush the buffer into the connection
374
+ def flush(flush_telemetry: false, sync: false)
375
+ forwarder.flush(flush_telemetry: flush_telemetry, sync: sync)
487
376
  end
488
377
 
489
- def tags_as_string(opts)
490
- if tag_arr = opts[:tags]
491
- tag_arr = tag_arr.map { |tag| escape_tag_content(tag) }
492
- tag_arr = tags + tag_arr # @tags are normalized when set, so not need to normalize them again
493
- else
494
- tag_arr = tags
495
- end
496
- tag_arr.join(COMMA) unless tag_arr.empty?
378
+ def telemetry
379
+ forwarder.telemetry
497
380
  end
498
381
 
499
- def escape_event_content(msg)
500
- msg.gsub NEW_LINE, ESC_NEW_LINE
382
+ def host
383
+ forwarder.host
501
384
  end
502
385
 
503
- def escape_tag_content(tag)
504
- tag = remove_pipes(tag.to_s)
505
- tag.delete! COMMA
506
- tag
386
+ def port
387
+ forwarder.port
507
388
  end
508
389
 
509
- def remove_pipes(msg)
510
- msg.delete PIPE
390
+ def socket_path
391
+ forwarder.socket_path
511
392
  end
512
393
 
513
- def escape_service_check_message(msg)
514
- escape_event_content(msg).gsub('m:'.freeze, 'm\:'.freeze)
394
+ def transport_type
395
+ forwarder.transport_type
515
396
  end
516
397
 
517
- def send_stats(stat, delta, type, opts=EMPTY_OPTIONS)
518
- sample_rate = opts[:sample_rate] || 1
519
- if sample_rate == 1 or rand < sample_rate
520
- full_stat = ''.dup
521
- full_stat << @prefix if @prefix
522
-
523
- stat = stat.is_a?(String) ? stat.dup : stat.to_s
524
- # Replace Ruby module scoping with '.' and reserved chars (: | @) with underscores.
525
- stat.gsub!(DOUBLE_COLON, DOT)
526
- stat.tr!(':|@'.freeze, UNDERSCORE)
527
- full_stat << stat
528
-
529
- full_stat << ':'.freeze
530
- full_stat << delta.to_s
531
- full_stat << PIPE
532
- full_stat << type
533
-
534
- unless sample_rate == 1
535
- full_stat << PIPE
536
- full_stat << '@'.freeze
537
- full_stat << sample_rate.to_s
538
- end
398
+ private
399
+ attr_reader :serializer
400
+ attr_reader :forwarder
539
401
 
540
- if tags_string = tags_as_string(opts)
541
- full_stat << PIPE
542
- full_stat << '#'.freeze
543
- full_stat << tags_string
544
- end
402
+ PROCESS_TIME_SUPPORTED = (RUBY_VERSION >= '2.1.0')
403
+ EMPTY_OPTIONS = {}.freeze
545
404
 
546
- send_stat(full_stat)
405
+ if PROCESS_TIME_SUPPORTED
406
+ def now
407
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
408
+ end
409
+ else
410
+ def now
411
+ Time.now.to_f
547
412
  end
548
413
  end
549
414
 
550
- def send_stat(message)
551
- if @batch.open?
552
- @batch.add message
553
- else
554
- @connection.write(message)
415
+ def send_stats(stat, delta, type, opts = EMPTY_OPTIONS)
416
+ telemetry.sent(metrics: 1) if telemetry
417
+
418
+ sample_rate = opts[:sample_rate] || @sample_rate || 1
419
+
420
+ if sample_rate == 1 || rand <= sample_rate
421
+ full_stat = serializer.to_stat(stat, delta, type, tags: opts[:tags], sample_rate: sample_rate)
422
+
423
+ forwarder.send_message(full_stat)
555
424
  end
556
425
  end
557
426
  end