dogstatsd-ruby 4.3.0 → 4.8.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.
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ class Batch
6
+ def initialize(connection, max_buffer_bytes)
7
+ @connection = connection
8
+ @max_buffer_bytes = max_buffer_bytes
9
+ @depth = 0
10
+ reset
11
+ end
12
+
13
+ def open
14
+ @depth += 1
15
+
16
+ yield
17
+ ensure
18
+ @depth -= 1
19
+ flush if !open?
20
+ end
21
+
22
+ def open?
23
+ @depth > 0
24
+ end
25
+
26
+ def add(message)
27
+ message_bytes = message.bytesize
28
+
29
+ unless @buffer_bytes == 0
30
+ if @buffer_bytes + 1 + message_bytes >= @max_buffer_bytes
31
+ flush
32
+ else
33
+ @buffer << "\n"
34
+ @buffer_bytes += 1
35
+ end
36
+ end
37
+
38
+ @buffer << message
39
+ @buffer_bytes += message_bytes
40
+ end
41
+
42
+ def flush
43
+ return if @buffer_bytes == 0
44
+ @connection.write(@buffer)
45
+ reset
46
+ end
47
+
48
+ private
49
+
50
+ def reset
51
+ @buffer = String.new
52
+ @buffer_bytes = 0
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ class Connection
6
+ def initialize(telemetry)
7
+ @telemetry = telemetry
8
+ end
9
+
10
+ # Close the underlying socket
11
+ def close
12
+ begin
13
+ @socket && @socket.close if instance_variable_defined?(:@socket)
14
+ rescue StandardError => boom
15
+ logger.error { "Statsd: #{boom.class} #{boom}" } if logger
16
+ end
17
+ @socket = nil
18
+ end
19
+
20
+ def write(payload)
21
+ logger.debug { "Statsd: #{payload}" } if logger
22
+
23
+ flush_telemetry = telemetry.flush?
24
+
25
+ payload += telemetry.flush if flush_telemetry
26
+
27
+ send_message(payload)
28
+
29
+ telemetry.reset if flush_telemetry
30
+
31
+ telemetry.sent(packets: 1, bytes: payload.length)
32
+ rescue StandardError => boom
33
+ # Try once to reconnect if the socket has been closed
34
+ retries ||= 1
35
+ if retries <= 1 &&
36
+ (boom.is_a?(Errno::ENOTCONN) or
37
+ boom.is_a?(Errno::ECONNREFUSED) or
38
+ boom.is_a?(IOError) && boom.message =~ /closed stream/i)
39
+ retries += 1
40
+ begin
41
+ close
42
+ retry
43
+ rescue StandardError => e
44
+ boom = e
45
+ end
46
+ end
47
+
48
+ telemetry.dropped(packets: 1, bytes: payload.length)
49
+ logger.error { "Statsd: #{boom.class} #{boom}" } if logger
50
+ nil
51
+ end
52
+
53
+ private
54
+ attr_reader :telemetry
55
+ attr_reader :logger
56
+
57
+ def socket
58
+ @socket ||= connect
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ module Serialization
6
+ end
7
+ end
8
+ end
9
+
10
+ require_relative 'serialization/tag_serializer'
11
+ require_relative 'serialization/service_check_serializer'
12
+ require_relative 'serialization/event_serializer'
13
+ require_relative 'serialization/stat_serializer'
14
+
15
+ require_relative 'serialization/serializer'
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ module Serialization
6
+ class EventSerializer
7
+ EVENT_BASIC_OPTIONS = {
8
+ date_happened: 'd:',
9
+ hostname: 'h:',
10
+ aggregation_key: 'k:',
11
+ priority: 'p:',
12
+ source_type_name: 's:',
13
+ alert_type: 't:',
14
+ }.freeze
15
+
16
+ def initialize(global_tags: [])
17
+ @tag_serializer = TagSerializer.new(global_tags)
18
+ end
19
+
20
+ def format(title, text, options = EMPTY_OPTIONS)
21
+ title = escape(title)
22
+ text = escape(text)
23
+
24
+ String.new.tap do |event|
25
+ event << '_e{'
26
+ event << title.bytesize.to_s
27
+ event << ','
28
+ event << text.bytesize.to_s
29
+ event << '}:'
30
+ event << title
31
+ event << '|'
32
+ event << text
33
+
34
+ # we are serializing the generic service check options
35
+ # before serializing specialized options that need edge-cases
36
+ EVENT_BASIC_OPTIONS.each do |option_key, shortcut|
37
+ if value = options[option_key]
38
+ event << '|'
39
+ event << shortcut
40
+ event << value.to_s.delete('|')
41
+ end
42
+ end
43
+
44
+ if raw_tags = options[:tags]
45
+ if tags = tag_serializer.format(raw_tags)
46
+ event << '|#'
47
+ event << tags
48
+ end
49
+ end
50
+
51
+ if event.bytesize > MAX_EVENT_SIZE
52
+ raise "Event #{title} payload is too big (more that 8KB), event discarded"
53
+ end
54
+ end
55
+ end
56
+
57
+ protected
58
+ attr_reader :tag_serializer
59
+
60
+ def escape(text)
61
+ text.delete('|').tap do |t|
62
+ t.gsub!("\n", '\n')
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # require 'forwardable'
4
+
5
+ module Datadog
6
+ class Statsd
7
+ module Serialization
8
+ class Serializer
9
+ def initialize(prefix: nil, global_tags: [])
10
+ @stat_serializer = StatSerializer.new(prefix, global_tags: global_tags)
11
+ @service_check_serializer = ServiceCheckSerializer.new(global_tags: global_tags)
12
+ @event_serializer = EventSerializer.new(global_tags: global_tags)
13
+ end
14
+
15
+ # using *args would make new allocations
16
+ def to_stat(name, delta, type, tags: [], sample_rate: 1)
17
+ stat_serializer.format(name, delta, type, tags: tags, sample_rate: sample_rate)
18
+ end
19
+
20
+ # using *args would make new allocations
21
+ def to_service_check(name, status, options = EMPTY_OPTIONS)
22
+ service_check_serializer.format(name, status, options)
23
+ end
24
+
25
+ # using *args would make new allocations
26
+ def to_event(title, text, options = EMPTY_OPTIONS)
27
+ event_serializer.format(title, text, options)
28
+ end
29
+
30
+ def global_tags
31
+ stat_serializer.global_tags
32
+ end
33
+
34
+ protected
35
+ attr_reader :stat_serializer
36
+ attr_reader :service_check_serializer
37
+ attr_reader :event_serializer
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ module Serialization
6
+ class ServiceCheckSerializer
7
+ SERVICE_CHECK_BASIC_OPTIONS = {
8
+ timestamp: 'd:',
9
+ hostname: 'h:',
10
+ }.freeze
11
+
12
+ def initialize(global_tags: [])
13
+ @tag_serializer = TagSerializer.new(global_tags)
14
+ end
15
+
16
+ def format(name, status, options = EMPTY_OPTIONS)
17
+ String.new.tap do |service_check|
18
+ # line basics
19
+ service_check << "_sc"
20
+ service_check << "|"
21
+ service_check << name.to_s
22
+ service_check << "|"
23
+ service_check << status.to_s
24
+
25
+ # we are serializing the generic service check options
26
+ # before serializing specialized options that need edge-cases
27
+ SERVICE_CHECK_BASIC_OPTIONS.each do |option_key, shortcut|
28
+ if value = options[option_key]
29
+ service_check << '|'
30
+ service_check << shortcut
31
+ service_check << value.to_s.delete('|')
32
+ end
33
+ end
34
+
35
+ if message = options[:message]
36
+ service_check << '|m:'
37
+ service_check << escape_message(message)
38
+ end
39
+
40
+ if raw_tags = options[:tags]
41
+ if tags = tag_serializer.format(raw_tags)
42
+ service_check << '|#'
43
+ service_check << tags
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ protected
50
+ attr_reader :tag_serializer
51
+
52
+ def escape_message(message)
53
+ message.delete('|').tap do |m|
54
+ m.gsub!("\n", '\n')
55
+ m.gsub!('m:', 'm\:')
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ module Serialization
6
+ class StatSerializer
7
+ def initialize(prefix, global_tags: [])
8
+ @prefix = prefix
9
+ @tag_serializer = TagSerializer.new(global_tags)
10
+ end
11
+
12
+ def format(name, delta, type, tags: [], sample_rate: 1)
13
+ String.new.tap do |stat|
14
+ stat << prefix if prefix
15
+
16
+ # stat value
17
+ stat << formated_name(name)
18
+ stat << ':'
19
+ stat << delta.to_s
20
+
21
+ # stat type
22
+ stat << '|'
23
+ stat << type
24
+
25
+ # sample_rate
26
+ if sample_rate != 1
27
+ stat << '|'
28
+ stat << '@'
29
+ stat << sample_rate.to_s
30
+ end
31
+
32
+ # tags
33
+ if tags_list = tag_serializer.format(tags)
34
+ stat << '|'
35
+ stat << '#'
36
+ stat << tags_list
37
+ end
38
+ end
39
+ end
40
+
41
+ def global_tags
42
+ tag_serializer.global_tags
43
+ end
44
+
45
+ private
46
+ attr_reader :prefix
47
+ attr_reader :tag_serializer
48
+
49
+ def formated_name(name)
50
+ formated = name.is_a?(String) ? name.dup : name.to_s
51
+
52
+ formated.tap do |f|
53
+ # replace Ruby module scoping with '.'
54
+ f.gsub!('::', '.')
55
+ # replace reserved chars (: | @) with underscores.
56
+ f.tr!(':|@', '_')
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ module Serialization
6
+ class TagSerializer
7
+ def initialize(global_tags = [], env = ENV)
8
+ # Convert to hash
9
+ global_tags = to_tags_hash(global_tags)
10
+
11
+ # Merge with default tags
12
+ global_tags = default_tags(env).merge(global_tags)
13
+
14
+ # Convert to tag list and set
15
+ @global_tags = to_tags_list(global_tags)
16
+ end
17
+
18
+ def format(message_tags)
19
+ # fast return global tags if there's no message_tags
20
+ # to avoid more allocations
21
+ tag_list = if message_tags && message_tags.any?
22
+ global_tags + to_tags_list(message_tags)
23
+ else
24
+ global_tags
25
+ end
26
+
27
+ tag_list.join(',') if tag_list.any?
28
+ end
29
+
30
+ attr_reader :global_tags
31
+
32
+ private
33
+
34
+ def to_tags_hash(tags)
35
+ case tags
36
+ when Hash
37
+ tags.dup
38
+ when Array
39
+ Hash[
40
+ tags.map do |string|
41
+ tokens = string.split(':')
42
+ tokens << nil if tokens.length == 1
43
+ tokens.length == 2 ? tokens : nil
44
+ end.compact
45
+ ]
46
+ else
47
+ {}
48
+ end
49
+ end
50
+
51
+ def to_tags_list(tags)
52
+ case tags
53
+ when Hash
54
+ tags.each_with_object([]) do |tag_pair, formatted_tags|
55
+ if tag_pair.last.nil?
56
+ formatted_tags << "#{tag_pair.first}"
57
+ else
58
+ formatted_tags << "#{tag_pair.first}:#{tag_pair.last}"
59
+ end
60
+ end
61
+ when Array
62
+ tags.dup
63
+ else
64
+ []
65
+ end.map! do |tag|
66
+ escape_tag_content(tag)
67
+ end
68
+ end
69
+
70
+ def escape_tag_content(tag)
71
+ tag.to_s.delete('|,')
72
+ end
73
+
74
+ def dd_tags(env = ENV)
75
+ return {} unless dd_tags = env['DD_TAGS']
76
+
77
+ to_tags_hash(dd_tags.split(','))
78
+ end
79
+
80
+ def default_tags(env = ENV)
81
+ dd_tags(env).tap do |tags|
82
+ tags['dd.internal.entity_id'] = env['DD_ENTITY_ID'] if env.key?('DD_ENTITY_ID')
83
+ tags['env'] = env['DD_ENV'] if env.key?('DD_ENV')
84
+ tags['service'] = env['DD_SERVICE'] if env.key?('DD_SERVICE')
85
+ tags['version'] = env['DD_VERSION'] if env.key?('DD_VERSION')
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end